diff --git a/Environments/App-Base-WebApp-ACA/abbreviations.json b/Environments/App-Base-WebApp-ACA/abbreviations.json new file mode 100644 index 00000000..289a08aa --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/abbreviations.json @@ -0,0 +1,136 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} \ No newline at end of file diff --git a/Environments/App-Base-WebApp-ACA/app/api.bicep b/Environments/App-Base-WebApp-ACA/app/api.bicep new file mode 100644 index 00000000..1df68145 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/app/api.bicep @@ -0,0 +1,75 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param applicationInsightsName string +param containerAppsEnvironmentName string +param containerRegistryName string +param keyVaultName string +param serviceName string = 'api' +param corsAcaUrl string +param exists bool + +resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +// Give the API access to KeyVault +module apiKeyVaultAccess '../core/security/keyvault-access.bicep' = { + name: 'api-keyvault-access' + params: { + keyVaultName: keyVaultName + principalId: apiIdentity.properties.principalId + } +} + +module app '../core/host/container-app-upsert.bicep' = { + name: '${serviceName}-container-app' + dependsOn: [ apiKeyVaultAccess ] + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identityType: 'UserAssigned' + identityName: apiIdentity.name + exists: exists + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: '1.0' + containerMemory: '2.0Gi' + env: [ + { + name: 'AZURE_CLIENT_ID' + value: apiIdentity.properties.clientId + } + { + name: 'AZURE_KEY_VAULT_ENDPOINT' + value: keyVault.properties.vaultUri + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'API_ALLOW_ORIGINS' + value: corsAcaUrl + } + ] + targetPort: 3100 + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output SERVICE_API_IDENTITY_PRINCIPAL_ID string = apiIdentity.properties.principalId +output SERVICE_API_NAME string = app.outputs.name +output SERVICE_API_URI string = app.outputs.uri +output SERVICE_API_IMAGE_NAME string = app.outputs.imageName diff --git a/Environments/App-Base-WebApp-ACA/app/apim-api-policy.xml b/Environments/App-Base-WebApp-ACA/app/apim-api-policy.xml new file mode 100644 index 00000000..d9ac2b2d --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/app/apim-api-policy.xml @@ -0,0 +1,92 @@ + + + + + + + + {origin} + + + PUT + GET + POST + DELETE + PATCH + + +
*
+
+ +
*
+
+
+ + + + + + + Call to the @(context.Api.Name) + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failed to process the @(context.Api.Name) + + + + + + + + + + + + + + + + + + An unexpected error has occurred. + + +
diff --git a/Environments/App-Base-WebApp-ACA/app/apim-api.bicep b/Environments/App-Base-WebApp-ACA/app/apim-api.bicep new file mode 100644 index 00000000..b6008404 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/app/apim-api.bicep @@ -0,0 +1,120 @@ +param name string + +@description('Resource name to uniquely identify this API within the API Management service instance') +@minLength(1) +param apiName string + +@description('The Display Name of the API') +@minLength(1) +@maxLength(300) +param apiDisplayName string + +@description('Description of the API. May include HTML formatting tags.') +@minLength(1) +param apiDescription string + +@description('Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API.') +@minLength(1) +param apiPath string + +@description('Absolute URL of the web frontend') +param webFrontendUrl string + +@description('Absolute URL of the backend service implementing this API.') +param apiBackendUrl string + +@description('Resource name for backend Web App or Function App') +param apiAppName string = '' + +var apiPolicyContent = replace(loadTextContent('./apim-api-policy.xml'), '{origin}', webFrontendUrl) + +resource restApi 'Microsoft.ApiManagement/service/apis@2021-12-01-preview' = { + name: apiName + parent: apimService + properties: { + description: apiDescription + displayName: apiDisplayName + path: apiPath + protocols: [ 'https' ] + subscriptionRequired: false + type: 'http' + format: 'openapi' + serviceUrl: apiBackendUrl + value: loadTextContent('./openapi.yaml') + } +} + +resource apiPolicy 'Microsoft.ApiManagement/service/apis/policies@2021-12-01-preview' = { + name: 'policy' + parent: restApi + properties: { + format: 'rawxml' + value: apiPolicyContent + } +} + +resource apiDiagnostics 'Microsoft.ApiManagement/service/apis/diagnostics@2021-12-01-preview' = { + name: 'applicationinsights' + parent: restApi + properties: { + alwaysLog: 'allErrors' + backend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + frontend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + httpCorrelationProtocol: 'W3C' + logClientIp: true + loggerId: apimLogger.id + metrics: true + sampling: { + percentage: 100 + samplingType: 'fixed' + } + verbosity: 'verbose' + } +} + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' existing = { + name: name +} + +// Necessary due to https://github.com/Azure/bicep/issues/9594 +// placeholderName is never deployed, it is merely used to make the child name validation pass +var appNameForBicep = !empty(apiAppName) ? apiAppName : 'placeholderName' + +resource apiAppProperties 'Microsoft.Web/sites/config@2022-03-01' = if (!empty(apiAppName)) { + name: '${appNameForBicep}/web' + kind: 'string' + properties: { + apiManagementConfig: { + id: '${apimService.id}/apis/${apiName}' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' existing = { + name: 'app-insights-logger' + parent: apimService +} + +output SERVICE_API_URI string = '${apimService.properties.gatewayUrl}/${apiPath}' diff --git a/Environments/App-Base-WebApp-ACA/app/db.bicep b/Environments/App-Base-WebApp-ACA/app/db.bicep new file mode 100644 index 00000000..938949c6 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/app/db.bicep @@ -0,0 +1,40 @@ +param accountName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [ + { + name: 'TodoList' + id: 'TodoList' + shardKey: 'Hash' + indexKey: '_id' + } + { + name: 'TodoItem' + id: 'TodoItem' + shardKey: 'Hash' + indexKey: '_id' + } +] +param databaseName string = '' +param keyVaultName string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module cosmos '../core/database/cosmos/mongo/cosmos-mongo-db.bicep' = { + name: 'cosmos-mongo' + params: { + accountName: accountName + databaseName: actualDatabaseName + location: location + collections: collections + keyVaultName: keyVaultName + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = cosmos.outputs.databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/App-Base-WebApp-ACA/app/openapi.yaml b/Environments/App-Base-WebApp-ACA/app/openapi.yaml new file mode 100644 index 00000000..c42e99ca --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/app/openapi.yaml @@ -0,0 +1,312 @@ +openapi: 3.0.0 +info: + description: Simple Todo API + version: 3.0.0 + title: Simple Todo API + contact: + email: azdevteam@microsoft.com + +components: + schemas: + TodoItem: + type: object + required: + - listId + - name + - description + description: A task that needs to be completed + properties: + id: + type: string + listId: + type: string + name: + type: string + description: + type: string + state: + $ref: "#/components/schemas/TodoState" + dueDate: + type: string + format: date-time + completedDate: + type: string + format: date-time + TodoList: + type: object + required: + - name + properties: + id: + type: string + name: + type: string + description: + type: string + description: " A list of related Todo items" + TodoState: + type: string + enum: + - todo + - inprogress + - done + parameters: + listId: + in: path + required: true + name: listId + description: The Todo list unique identifier + schema: + type: string + itemId: + in: path + required: true + name: itemId + description: The Todo item unique identifier + schema: + type: string + state: + in: path + required: true + name: state + description: The Todo item state + schema: + $ref: "#/components/schemas/TodoState" + top: + in: query + required: false + name: top + description: The max number of items to returns in a result + schema: + type: number + default: 20 + skip: + in: query + required: false + name: skip + description: The number of items to skip within the results + schema: + type: number + default: 0 + + requestBodies: + TodoList: + description: The Todo List + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: The Todo Item + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + + responses: + TodoList: + description: A Todo list result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoListArray: + description: An array of Todo lists + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: A Todo item result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + TodoItemArray: + description: An array of Todo items + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoItem" + +paths: + /lists: + get: + operationId: GetLists + summary: Gets an array of Todo lists + tags: + - Lists + parameters: + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoListArray" + post: + operationId: CreateList + summary: Creates a new Todo list + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + responses: + 201: + $ref: "#/components/responses/TodoList" + 400: + description: Invalid request schema + /lists/{listId}: + get: + operationId: GetListById + summary: Gets a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + put: + operationId: UpdateListById + summary: Updates a Todo list by unique identifier + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + 400: + description: Todo list is invalid + delete: + operationId: DeleteListById + summary: Deletes a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 204: + description: Todo list deleted successfully + 404: + description: Todo list not found + /lists/{listId}/items: + post: + operationId: CreateItem + summary: Creates a new Todo item within a list + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 201: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list not found + get: + operationId: GetItemsByListId + summary: Gets Todo items within the specified list + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list not found + /lists/{listId}/items/{itemId}: + get: + operationId: GetItemById + summary: Gets a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemById + summary: Updates a Todo item by unique identifier + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 400: + description: Todo item is invalid + 404: + description: Todo list or item not found + delete: + operationId: DeleteItemById + summary: Deletes a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 204: + description: Todo item deleted successfully + 404: + description: Todo list or item not found + /lists/{listId}/items/state/{state}: + get: + operationId: GetItemsByListIdAndState + summary: Gets a list of Todo items of a specific state + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemsStateByListId + summary: Changes the state of the specified list items + tags: + - Items + requestBody: + description: unique identifiers of the Todo items to update + content: + application/json: + schema: + type: array + items: + description: The Todo item unique identifier + type: string + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + responses: + 204: + description: Todo items updated + 400: + description: Update request is invalid diff --git a/Environments/App-Base-WebApp-ACA/app/web.bicep b/Environments/App-Base-WebApp-ACA/app/web.bicep new file mode 100644 index 00000000..2c692278 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/app/web.bicep @@ -0,0 +1,54 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param apiBaseUrl string +param applicationInsightsName string +param containerAppsEnvironmentName string +param containerRegistryName string +param serviceName string = 'web' +param exists bool + +resource webIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +module app '../core/host/container-app-upsert.bicep' = { + name: '${serviceName}-container-app' + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identityType: 'UserAssigned' + identityName: identityName + exists: exists + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + env: [ + { + name: 'REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'REACT_APP_API_BASE_URL' + value: apiBaseUrl + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + ] + targetPort: 80 + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +output SERVICE_WEB_IDENTITY_PRINCIPAL_ID string = webIdentity.properties.principalId +output SERVICE_WEB_NAME string = app.outputs.name +output SERVICE_WEB_URI string = app.outputs.uri +output SERVICE_WEB_IMAGE_NAME string = app.outputs.imageName diff --git a/Environments/App-Base-WebApp-ACA/azuredeploy.json b/Environments/App-Base-WebApp-ACA/azuredeploy.json new file mode 100644 index 00000000..65c1d9f7 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/azuredeploy.json @@ -0,0 +1,4679 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "5915759848307494038" + } + }, + "parameters": { + "environmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the the environment which is used to generate a short unique hash used in all resources." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "minLength": 1, + "metadata": { + "description": "Primary location for all resources" + } + }, + "apiContainerAppName": { + "type": "string", + "defaultValue": "" + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + }, + "containerAppsEnvironmentName": { + "type": "string", + "defaultValue": "" + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "" + }, + "cosmosAccountName": { + "type": "string", + "defaultValue": "" + }, + "cosmosDatabaseName": { + "type": "string", + "defaultValue": "" + }, + "keyVaultName": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "" + }, + "webContainerAppName": { + "type": "string", + "defaultValue": "" + }, + "apimServiceName": { + "type": "string", + "defaultValue": "" + }, + "apiAppExists": { + "type": "bool", + "defaultValue": false + }, + "webAppExists": { + "type": "bool", + "defaultValue": false + }, + "useAPIM": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Flag to use Azure API Management to mediate the calls between the Web frontend and the backend API" + } + }, + "principalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Id of the user or app to assign application roles" + } + }, + "webApiBaseUrl": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The base URL used by the web service for sending API requests" + } + } + }, + "variables": { + "$fxv#0": { + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" + }, + "abbrs": "[variables('$fxv#0')]", + "resourceToken": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "tags": { + "azd-env-name": "[parameters('environmentName')]" + }, + "apiContainerAppNameOrDefault": "[format('{0}web-{1}', variables('abbrs').appContainerApps, variables('resourceToken'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-apps", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "app" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "containerAppsEnvironmentName": "[if(not(empty(parameters('containerAppsEnvironmentName'))), createObject('value', parameters('containerAppsEnvironmentName')), createObject('value', format('{0}{1}', variables('abbrs').appManagedEnvironments, variables('resourceToken'))))]", + "containerRegistryName": "[if(not(empty(parameters('containerRegistryName'))), createObject('value', parameters('containerRegistryName')), createObject('value', format('{0}{1}', variables('abbrs').containerRegistryRegistries, variables('resourceToken'))))]", + "containerRegistryAdminUserEnabled": { + "value": true + }, + "logAnalyticsWorkspaceName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.logAnalyticsWorkspaceName.value]" + }, + "applicationInsightsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "14116108111976192358" + }, + "description": "Creates an Azure Container Registry and an Azure Container Apps environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "containerAppsEnvironmentName": { + "type": "string" + }, + "containerRegistryName": { + "type": "string" + }, + "containerRegistryResourceGroupName": { + "type": "string", + "defaultValue": "" + }, + "containerRegistryAdminUserEnabled": { + "type": "bool", + "defaultValue": false + }, + "logAnalyticsWorkspaceName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-container-apps-environment', parameters('name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "logAnalyticsWorkspaceName": { + "value": "[parameters('logAnalyticsWorkspaceName')]" + }, + "applicationInsightsName": { + "value": "[parameters('applicationInsightsName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "4766903245227392386" + }, + "description": "Creates an Azure Container Apps environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Application Insights resource" + } + }, + "daprEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Specifies if Dapr is enabled" + } + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace" + } + } + }, + "resources": [ + { + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2023-04-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "appLogsConfiguration": { + "destination": "log-analytics", + "logAnalyticsConfiguration": { + "customerId": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').customerId]", + "sharedKey": "[listKeys(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').primarySharedKey]" + } + }, + "daprAIInstrumentationKey": "[if(and(parameters('daprEnabled'), not(empty(parameters('applicationInsightsName')))), reference(resourceId('Microsoft.Insights/components', parameters('applicationInsightsName')), '2020-02-02').InstrumentationKey, '')]" + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2023-04-01-preview').defaultDomain]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-container-registry', parameters('name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerRegistryName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "adminUserEnabled": { + "value": "[parameters('containerRegistryAdminUserEnabled')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "12834334744516280883" + }, + "description": "Creates an Azure Container Registry." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "adminUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether admin user is enabled" + } + }, + "anonymousPullEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether anonymous pull is enabled" + } + }, + "dataEndpointEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether data endpoint is enabled" + } + }, + "encryption": { + "type": "object", + "defaultValue": { + "status": "disabled" + }, + "metadata": { + "description": "Encryption settings" + } + }, + "networkRuleBypassOptions": { + "type": "string", + "defaultValue": "AzureServices", + "metadata": { + "description": "Options for bypassing network rules" + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Public network access setting" + } + }, + "sku": { + "type": "object", + "defaultValue": { + "name": "Basic" + }, + "metadata": { + "description": "SKU settings" + } + }, + "zoneRedundancy": { + "type": "string", + "defaultValue": "Disabled", + "metadata": { + "description": "Zone redundancy setting" + } + }, + "workspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The log analytics workspace ID used for logging and monitoring" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2022-02-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": "[parameters('sku')]", + "properties": { + "adminUserEnabled": "[parameters('adminUserEnabled')]", + "anonymousPullEnabled": "[parameters('anonymousPullEnabled')]", + "dataEndpointEnabled": "[parameters('dataEndpointEnabled')]", + "encryption": "[parameters('encryption')]", + "networkRuleBypassOptions": "[parameters('networkRuleBypassOptions')]", + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "zoneRedundancy": "[parameters('zoneRedundancy')]" + } + }, + { + "condition": "[not(empty(parameters('workspaceId')))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "registry-diagnostics", + "properties": { + "workspaceId": "[parameters('workspaceId')]", + "logs": [ + { + "category": "ContainerRegistryRepositoryEvents", + "enabled": true + }, + { + "category": "ContainerRegistryLoginEvents", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true, + "timeGrain": "PT1M" + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" + ] + } + ], + "outputs": { + "loginServer": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), '2022-02-01-preview').loginServer]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.defaultDomain.value]" + }, + "environmentName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.name.value]" + }, + "environmentId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.id.value]" + }, + "registryLoginServer": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-registry', parameters('name'))), '2022-09-01').outputs.loginServer.value]" + }, + "registryName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-registry', parameters('name'))), '2022-09-01').outputs.name.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "web", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(not(empty(parameters('webContainerAppName'))), createObject('value', parameters('webContainerAppName')), createObject('value', format('{0}web-{1}', variables('abbrs').appContainerApps, variables('resourceToken'))))]", + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "identityName": { + "value": "[format('{0}web-{1}', variables('abbrs').managedIdentityUserAssignedIdentities, variables('resourceToken'))]" + }, + "apiBaseUrl": "[if(not(empty(parameters('webApiBaseUrl'))), createObject('value', parameters('webApiBaseUrl')), createObject('value', reference(resourceId('Microsoft.Resources/deployments', 'api'), '2022-09-01').outputs.SERVICE_API_URI.value))]", + "applicationInsightsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + }, + "containerAppsEnvironmentName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.environmentName.value]" + }, + "containerRegistryName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.registryName.value]" + }, + "exists": { + "value": "[parameters('webAppExists')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "5244656399300381833" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "identityName": { + "type": "string" + }, + "apiBaseUrl": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "containerAppsEnvironmentName": { + "type": "string" + }, + "containerRegistryName": { + "type": "string" + }, + "serviceName": { + "type": "string", + "defaultValue": "web" + }, + "exists": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "location": "[parameters('location')]" + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-container-app', parameters('serviceName'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[union(parameters('tags'), createObject('azd-service-name', parameters('serviceName')))]" + }, + "identityType": { + "value": "UserAssigned" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "exists": { + "value": "[parameters('exists')]" + }, + "containerAppsEnvironmentName": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "containerRegistryName": { + "value": "[parameters('containerRegistryName')]" + }, + "env": { + "value": [ + { + "name": "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('applicationInsightsName')), '2020-02-02').ConnectionString]" + }, + { + "name": "REACT_APP_API_BASE_URL", + "value": "[parameters('apiBaseUrl')]" + }, + { + "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('applicationInsightsName')), '2020-02-02').ConnectionString]" + } + ] + }, + "targetPort": { + "value": 80 + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "17242409915151931414" + }, + "description": "Creates or updates an existing Azure Container App." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "containerAppsEnvironmentName": { + "type": "string", + "metadata": { + "description": "The environment name for the container apps" + } + }, + "containerCpuCoreCount": { + "type": "string", + "defaultValue": "0.5", + "metadata": { + "description": "The number of CPU cores allocated to a single container instance, e.g., 0.5" + } + }, + "containerMaxReplicas": { + "type": "int", + "defaultValue": 10, + "minValue": 1, + "metadata": { + "description": "The maximum number of replicas to run. Must be at least 1." + } + }, + "containerMemory": { + "type": "string", + "defaultValue": "1.0Gi", + "metadata": { + "description": "The amount of memory allocated to a single container instance, e.g., 1Gi" + } + }, + "containerMinReplicas": { + "type": "int", + "defaultValue": 1, + "minValue": 1, + "metadata": { + "description": "The minimum number of replicas to run. Must be at least 1." + } + }, + "containerName": { + "type": "string", + "defaultValue": "main", + "metadata": { + "description": "The name of the container" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container registry" + } + }, + "daprAppProtocol": { + "type": "string", + "defaultValue": "http", + "allowedValues": [ + "http", + "grpc" + ], + "metadata": { + "description": "The protocol used by Dapr to connect to the app, e.g., HTTP or gRPC" + } + }, + "daprEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable or disable Dapr for the container app" + } + }, + "daprAppId": { + "type": "string", + "defaultValue": "[parameters('containerName')]", + "metadata": { + "description": "The Dapr app ID" + } + }, + "exists": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Specifies if the resource already exists" + } + }, + "ingressEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Specifies if Ingress is enabled for the container app" + } + }, + "identityType": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "None", + "SystemAssigned", + "UserAssigned" + ], + "metadata": { + "description": "The type of identity for the resource" + } + }, + "identityName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the user-assigned identity" + } + }, + "imageName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container image" + } + }, + "secrets": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The secrets required for the container" + } + }, + "env": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The environment variables for the container" + } + }, + "external": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Specifies if the resource ingress is exposed externally" + } + }, + "serviceBinds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The service binds associated with the container" + } + }, + "targetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "The target port for the container" + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-update', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "identityType": { + "value": "[parameters('identityType')]" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "ingressEnabled": { + "value": "[parameters('ingressEnabled')]" + }, + "containerName": { + "value": "[parameters('containerName')]" + }, + "containerAppsEnvironmentName": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "containerRegistryName": { + "value": "[parameters('containerRegistryName')]" + }, + "containerCpuCoreCount": { + "value": "[parameters('containerCpuCoreCount')]" + }, + "containerMemory": { + "value": "[parameters('containerMemory')]" + }, + "containerMinReplicas": { + "value": "[parameters('containerMinReplicas')]" + }, + "containerMaxReplicas": { + "value": "[parameters('containerMaxReplicas')]" + }, + "daprEnabled": { + "value": "[parameters('daprEnabled')]" + }, + "daprAppId": { + "value": "[parameters('daprAppId')]" + }, + "daprAppProtocol": { + "value": "[parameters('daprAppProtocol')]" + }, + "secrets": { + "value": "[parameters('secrets')]" + }, + "external": { + "value": "[parameters('external')]" + }, + "env": { + "value": "[parameters('env')]" + }, + "imageName": "[if(not(empty(parameters('imageName'))), createObject('value', parameters('imageName')), if(parameters('exists'), createObject('value', reference(resourceId('Microsoft.App/containerApps', parameters('name')), '2023-04-01-preview').template.containers[0].image), createObject('value', '')))]", + "targetPort": { + "value": "[parameters('targetPort')]" + }, + "serviceBinds": { + "value": "[parameters('serviceBinds')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1912096201798605494" + }, + "description": "Creates a container app in an Azure Container App environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "allowedOrigins": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Allowed origins" + } + }, + "containerAppsEnvironmentName": { + "type": "string", + "metadata": { + "description": "Name of the environment for container apps" + } + }, + "containerCpuCoreCount": { + "type": "string", + "defaultValue": "0.5", + "metadata": { + "description": "CPU cores allocated to a single container instance, e.g., 0.5" + } + }, + "containerMaxReplicas": { + "type": "int", + "defaultValue": 10, + "minValue": 1, + "metadata": { + "description": "The maximum number of replicas to run. Must be at least 1." + } + }, + "containerMemory": { + "type": "string", + "defaultValue": "1.0Gi", + "metadata": { + "description": "Memory allocated to a single container instance, e.g., 1Gi" + } + }, + "containerMinReplicas": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "The minimum number of replicas to run. Must be at least 1." + } + }, + "containerName": { + "type": "string", + "defaultValue": "main", + "metadata": { + "description": "The name of the container" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container registry" + } + }, + "daprAppProtocol": { + "type": "string", + "defaultValue": "http", + "allowedValues": [ + "http", + "grpc" + ], + "metadata": { + "description": "The protocol used by Dapr to connect to the app, e.g., http or grpc" + } + }, + "daprAppId": { + "type": "string", + "defaultValue": "[parameters('containerName')]", + "metadata": { + "description": "The Dapr app ID" + } + }, + "daprEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable Dapr" + } + }, + "env": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The environment variables for the container" + } + }, + "external": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Specifies if the resource ingress is exposed externally" + } + }, + "identityName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the user-assigned identity" + } + }, + "identityType": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "None", + "SystemAssigned", + "UserAssigned" + ], + "metadata": { + "description": "The type of identity for the resource" + } + }, + "imageName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container image" + } + }, + "ingressEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Specifies if Ingress is enabled for the container app" + } + }, + "revisionMode": { + "type": "string", + "defaultValue": "Single" + }, + "secrets": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The secrets required for the container" + } + }, + "serviceBinds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The service binds associated with the container" + } + }, + "serviceType": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container apps add-on to use. e.g. redis" + } + }, + "targetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "The target port for the container" + } + } + }, + "variables": { + "usePrivateRegistry": "[and(not(empty(parameters('identityName'))), not(empty(parameters('containerRegistryName'))))]", + "normalizedIdentityType": "[if(not(empty(parameters('identityName'))), 'UserAssigned', parameters('identityType'))]" + }, + "resources": [ + { + "type": "Microsoft.App/containerApps", + "apiVersion": "2023-04-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "[variables('normalizedIdentityType')]", + "userAssignedIdentities": "[if(and(not(empty(parameters('identityName'))), equals(variables('normalizedIdentityType'), 'UserAssigned')), createObject(format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))), createObject()), null())]" + }, + "properties": { + "managedEnvironmentId": "[resourceId('Microsoft.App/managedEnvironments', parameters('containerAppsEnvironmentName'))]", + "configuration": { + "activeRevisionsMode": "[parameters('revisionMode')]", + "ingress": "[if(parameters('ingressEnabled'), createObject('external', parameters('external'), 'targetPort', parameters('targetPort'), 'transport', 'auto', 'corsPolicy', createObject('allowedOrigins', union(createArray('https://portal.azure.com', 'https://ms.portal.azure.com'), parameters('allowedOrigins')))), null())]", + "dapr": "[if(parameters('daprEnabled'), createObject('enabled', true(), 'appId', parameters('daprAppId'), 'appProtocol', parameters('daprAppProtocol'), 'appPort', if(parameters('ingressEnabled'), parameters('targetPort'), 0)), createObject('enabled', false()))]", + "secrets": "[parameters('secrets')]", + "service": "[if(not(empty(parameters('serviceType'))), createObject('type', parameters('serviceType')), null())]", + "registries": "[if(variables('usePrivateRegistry'), createArray(createObject('server', format('{0}.azurecr.io', parameters('containerRegistryName')), 'identity', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))), createArray())]" + }, + "template": { + "serviceBinds": "[if(not(empty(parameters('serviceBinds'))), parameters('serviceBinds'), null())]", + "containers": [ + { + "image": "[if(not(empty(parameters('imageName'))), parameters('imageName'), 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest')]", + "name": "[parameters('containerName')]", + "env": "[parameters('env')]", + "resources": { + "cpu": "[json(parameters('containerCpuCoreCount'))]", + "memory": "[parameters('containerMemory')]" + } + } + ], + "scale": { + "minReplicas": "[parameters('containerMinReplicas')]", + "maxReplicas": "[parameters('containerMaxReplicas')]" + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-registry-access', deployment().name))]" + ] + }, + { + "condition": "[variables('usePrivateRegistry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-registry-access', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerRegistryName": { + "value": "[parameters('containerRegistryName')]" + }, + "principalId": "[if(variables('usePrivateRegistry'), createObject('value', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), '2023-01-31').principalId), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "15144906240959446537" + }, + "description": "Assigns ACR Pull permissions to access an Azure Container Registry." + }, + "parameters": { + "containerRegistryName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "acrPullRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('containerRegistryName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('acrPullRole'))]", + "properties": { + "roleDefinitionId": "[variables('acrPullRole')]", + "principalType": "ServicePrincipal", + "principalId": "[parameters('principalId')]" + } + } + ] + } + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('containerAppsEnvironmentName')), '2023-04-01-preview').defaultDomain]" + }, + "identityPrincipalId": { + "type": "string", + "value": "[if(equals(variables('normalizedIdentityType'), 'None'), '', if(empty(parameters('identityName')), reference(resourceId('Microsoft.App/containerApps', parameters('name')), '2023-04-01-preview', 'full').identity.principalId, reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), '2023-01-31').principalId))]" + }, + "imageName": { + "type": "string", + "value": "[parameters('imageName')]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "serviceBind": { + "type": "object", + "value": "[if(not(empty(parameters('serviceType'))), createObject('serviceId', resourceId('Microsoft.App/containerApps', parameters('name')), 'name', parameters('name')), createObject())]" + }, + "uri": { + "type": "string", + "value": "[if(parameters('ingressEnabled'), format('https://{0}', reference(resourceId('Microsoft.App/containerApps', parameters('name')), '2023-04-01-preview').configuration.ingress.fqdn), '')]" + } + } + } + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-update', deployment().name)), '2022-09-01').outputs.defaultDomain.value]" + }, + "imageName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-update', deployment().name)), '2022-09-01').outputs.imageName.value]" + }, + "name": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-update', deployment().name)), '2022-09-01').outputs.name.value]" + }, + "uri": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-update', deployment().name)), '2022-09-01').outputs.uri.value]" + } + } + } + } + } + ], + "outputs": { + "SERVICE_WEB_IDENTITY_PRINCIPAL_ID": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), '2023-01-31').principalId]" + }, + "SERVICE_WEB_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-app', parameters('serviceName'))), '2022-09-01').outputs.name.value]" + }, + "SERVICE_WEB_URI": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-app', parameters('serviceName'))), '2022-09-01').outputs.uri.value]" + }, + "SERVICE_WEB_IMAGE_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-app', parameters('serviceName'))), '2022-09-01').outputs.imageName.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'api')]", + "[resourceId('Microsoft.Resources/deployments', 'container-apps')]", + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "api", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(not(empty(parameters('apiContainerAppName'))), createObject('value', parameters('apiContainerAppName')), createObject('value', format('{0}api-{1}', variables('abbrs').appContainerApps, variables('resourceToken'))))]", + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "identityName": { + "value": "[format('{0}api-{1}', variables('abbrs').managedIdentityUserAssignedIdentities, variables('resourceToken'))]" + }, + "applicationInsightsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + }, + "containerAppsEnvironmentName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.environmentName.value]" + }, + "containerRegistryName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.registryName.value]" + }, + "keyVaultName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyvault'), '2022-09-01').outputs.name.value]" + }, + "corsAcaUrl": { + "value": "[format('https://{0}.{1}', variables('apiContainerAppNameOrDefault'), reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.defaultDomain.value)]" + }, + "exists": { + "value": "[parameters('apiAppExists')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "11092891629527222377" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "identityName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "containerAppsEnvironmentName": { + "type": "string" + }, + "containerRegistryName": { + "type": "string" + }, + "keyVaultName": { + "type": "string" + }, + "serviceName": { + "type": "string", + "defaultValue": "api" + }, + "corsAcaUrl": { + "type": "string" + }, + "exists": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "location": "[parameters('location')]" + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "api-keyvault-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "principalId": { + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), '2023-01-31').principalId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "815983560956742247" + }, + "description": "Assigns an Azure Key Vault access policy." + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "add" + }, + "keyVaultName": { + "type": "string" + }, + "permissions": { + "type": "object", + "defaultValue": { + "secrets": [ + "get", + "list" + ] + } + }, + "principalId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "properties": { + "accessPolicies": [ + { + "objectId": "[parameters('principalId')]", + "tenantId": "[subscription().tenantId]", + "permissions": "[parameters('permissions')]" + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-container-app', parameters('serviceName'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[union(parameters('tags'), createObject('azd-service-name', parameters('serviceName')))]" + }, + "identityType": { + "value": "UserAssigned" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "exists": { + "value": "[parameters('exists')]" + }, + "containerAppsEnvironmentName": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "containerRegistryName": { + "value": "[parameters('containerRegistryName')]" + }, + "containerCpuCoreCount": { + "value": "1.0" + }, + "containerMemory": { + "value": "2.0Gi" + }, + "env": { + "value": [ + { + "name": "AZURE_CLIENT_ID", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), '2023-01-31').clientId]" + }, + { + "name": "AZURE_KEY_VAULT_ENDPOINT", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2022-07-01').vaultUri]" + }, + { + "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('applicationInsightsName')), '2020-02-02').ConnectionString]" + }, + { + "name": "API_ALLOW_ORIGINS", + "value": "[parameters('corsAcaUrl')]" + } + ] + }, + "targetPort": { + "value": 3100 + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "17242409915151931414" + }, + "description": "Creates or updates an existing Azure Container App." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "containerAppsEnvironmentName": { + "type": "string", + "metadata": { + "description": "The environment name for the container apps" + } + }, + "containerCpuCoreCount": { + "type": "string", + "defaultValue": "0.5", + "metadata": { + "description": "The number of CPU cores allocated to a single container instance, e.g., 0.5" + } + }, + "containerMaxReplicas": { + "type": "int", + "defaultValue": 10, + "minValue": 1, + "metadata": { + "description": "The maximum number of replicas to run. Must be at least 1." + } + }, + "containerMemory": { + "type": "string", + "defaultValue": "1.0Gi", + "metadata": { + "description": "The amount of memory allocated to a single container instance, e.g., 1Gi" + } + }, + "containerMinReplicas": { + "type": "int", + "defaultValue": 1, + "minValue": 1, + "metadata": { + "description": "The minimum number of replicas to run. Must be at least 1." + } + }, + "containerName": { + "type": "string", + "defaultValue": "main", + "metadata": { + "description": "The name of the container" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container registry" + } + }, + "daprAppProtocol": { + "type": "string", + "defaultValue": "http", + "allowedValues": [ + "http", + "grpc" + ], + "metadata": { + "description": "The protocol used by Dapr to connect to the app, e.g., HTTP or gRPC" + } + }, + "daprEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable or disable Dapr for the container app" + } + }, + "daprAppId": { + "type": "string", + "defaultValue": "[parameters('containerName')]", + "metadata": { + "description": "The Dapr app ID" + } + }, + "exists": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Specifies if the resource already exists" + } + }, + "ingressEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Specifies if Ingress is enabled for the container app" + } + }, + "identityType": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "None", + "SystemAssigned", + "UserAssigned" + ], + "metadata": { + "description": "The type of identity for the resource" + } + }, + "identityName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the user-assigned identity" + } + }, + "imageName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container image" + } + }, + "secrets": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The secrets required for the container" + } + }, + "env": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The environment variables for the container" + } + }, + "external": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Specifies if the resource ingress is exposed externally" + } + }, + "serviceBinds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The service binds associated with the container" + } + }, + "targetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "The target port for the container" + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-update', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "identityType": { + "value": "[parameters('identityType')]" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "ingressEnabled": { + "value": "[parameters('ingressEnabled')]" + }, + "containerName": { + "value": "[parameters('containerName')]" + }, + "containerAppsEnvironmentName": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "containerRegistryName": { + "value": "[parameters('containerRegistryName')]" + }, + "containerCpuCoreCount": { + "value": "[parameters('containerCpuCoreCount')]" + }, + "containerMemory": { + "value": "[parameters('containerMemory')]" + }, + "containerMinReplicas": { + "value": "[parameters('containerMinReplicas')]" + }, + "containerMaxReplicas": { + "value": "[parameters('containerMaxReplicas')]" + }, + "daprEnabled": { + "value": "[parameters('daprEnabled')]" + }, + "daprAppId": { + "value": "[parameters('daprAppId')]" + }, + "daprAppProtocol": { + "value": "[parameters('daprAppProtocol')]" + }, + "secrets": { + "value": "[parameters('secrets')]" + }, + "external": { + "value": "[parameters('external')]" + }, + "env": { + "value": "[parameters('env')]" + }, + "imageName": "[if(not(empty(parameters('imageName'))), createObject('value', parameters('imageName')), if(parameters('exists'), createObject('value', reference(resourceId('Microsoft.App/containerApps', parameters('name')), '2023-04-01-preview').template.containers[0].image), createObject('value', '')))]", + "targetPort": { + "value": "[parameters('targetPort')]" + }, + "serviceBinds": { + "value": "[parameters('serviceBinds')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1912096201798605494" + }, + "description": "Creates a container app in an Azure Container App environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "allowedOrigins": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Allowed origins" + } + }, + "containerAppsEnvironmentName": { + "type": "string", + "metadata": { + "description": "Name of the environment for container apps" + } + }, + "containerCpuCoreCount": { + "type": "string", + "defaultValue": "0.5", + "metadata": { + "description": "CPU cores allocated to a single container instance, e.g., 0.5" + } + }, + "containerMaxReplicas": { + "type": "int", + "defaultValue": 10, + "minValue": 1, + "metadata": { + "description": "The maximum number of replicas to run. Must be at least 1." + } + }, + "containerMemory": { + "type": "string", + "defaultValue": "1.0Gi", + "metadata": { + "description": "Memory allocated to a single container instance, e.g., 1Gi" + } + }, + "containerMinReplicas": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "The minimum number of replicas to run. Must be at least 1." + } + }, + "containerName": { + "type": "string", + "defaultValue": "main", + "metadata": { + "description": "The name of the container" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container registry" + } + }, + "daprAppProtocol": { + "type": "string", + "defaultValue": "http", + "allowedValues": [ + "http", + "grpc" + ], + "metadata": { + "description": "The protocol used by Dapr to connect to the app, e.g., http or grpc" + } + }, + "daprAppId": { + "type": "string", + "defaultValue": "[parameters('containerName')]", + "metadata": { + "description": "The Dapr app ID" + } + }, + "daprEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable Dapr" + } + }, + "env": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The environment variables for the container" + } + }, + "external": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Specifies if the resource ingress is exposed externally" + } + }, + "identityName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the user-assigned identity" + } + }, + "identityType": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "None", + "SystemAssigned", + "UserAssigned" + ], + "metadata": { + "description": "The type of identity for the resource" + } + }, + "imageName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container image" + } + }, + "ingressEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Specifies if Ingress is enabled for the container app" + } + }, + "revisionMode": { + "type": "string", + "defaultValue": "Single" + }, + "secrets": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The secrets required for the container" + } + }, + "serviceBinds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The service binds associated with the container" + } + }, + "serviceType": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the container apps add-on to use. e.g. redis" + } + }, + "targetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "The target port for the container" + } + } + }, + "variables": { + "usePrivateRegistry": "[and(not(empty(parameters('identityName'))), not(empty(parameters('containerRegistryName'))))]", + "normalizedIdentityType": "[if(not(empty(parameters('identityName'))), 'UserAssigned', parameters('identityType'))]" + }, + "resources": [ + { + "type": "Microsoft.App/containerApps", + "apiVersion": "2023-04-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "[variables('normalizedIdentityType')]", + "userAssignedIdentities": "[if(and(not(empty(parameters('identityName'))), equals(variables('normalizedIdentityType'), 'UserAssigned')), createObject(format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))), createObject()), null())]" + }, + "properties": { + "managedEnvironmentId": "[resourceId('Microsoft.App/managedEnvironments', parameters('containerAppsEnvironmentName'))]", + "configuration": { + "activeRevisionsMode": "[parameters('revisionMode')]", + "ingress": "[if(parameters('ingressEnabled'), createObject('external', parameters('external'), 'targetPort', parameters('targetPort'), 'transport', 'auto', 'corsPolicy', createObject('allowedOrigins', union(createArray('https://portal.azure.com', 'https://ms.portal.azure.com'), parameters('allowedOrigins')))), null())]", + "dapr": "[if(parameters('daprEnabled'), createObject('enabled', true(), 'appId', parameters('daprAppId'), 'appProtocol', parameters('daprAppProtocol'), 'appPort', if(parameters('ingressEnabled'), parameters('targetPort'), 0)), createObject('enabled', false()))]", + "secrets": "[parameters('secrets')]", + "service": "[if(not(empty(parameters('serviceType'))), createObject('type', parameters('serviceType')), null())]", + "registries": "[if(variables('usePrivateRegistry'), createArray(createObject('server', format('{0}.azurecr.io', parameters('containerRegistryName')), 'identity', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))), createArray())]" + }, + "template": { + "serviceBinds": "[if(not(empty(parameters('serviceBinds'))), parameters('serviceBinds'), null())]", + "containers": [ + { + "image": "[if(not(empty(parameters('imageName'))), parameters('imageName'), 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest')]", + "name": "[parameters('containerName')]", + "env": "[parameters('env')]", + "resources": { + "cpu": "[json(parameters('containerCpuCoreCount'))]", + "memory": "[parameters('containerMemory')]" + } + } + ], + "scale": { + "minReplicas": "[parameters('containerMinReplicas')]", + "maxReplicas": "[parameters('containerMaxReplicas')]" + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-registry-access', deployment().name))]" + ] + }, + { + "condition": "[variables('usePrivateRegistry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-registry-access', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerRegistryName": { + "value": "[parameters('containerRegistryName')]" + }, + "principalId": "[if(variables('usePrivateRegistry'), createObject('value', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), '2023-01-31').principalId), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "15144906240959446537" + }, + "description": "Assigns ACR Pull permissions to access an Azure Container Registry." + }, + "parameters": { + "containerRegistryName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "acrPullRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('containerRegistryName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('acrPullRole'))]", + "properties": { + "roleDefinitionId": "[variables('acrPullRole')]", + "principalType": "ServicePrincipal", + "principalId": "[parameters('principalId')]" + } + } + ] + } + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('containerAppsEnvironmentName')), '2023-04-01-preview').defaultDomain]" + }, + "identityPrincipalId": { + "type": "string", + "value": "[if(equals(variables('normalizedIdentityType'), 'None'), '', if(empty(parameters('identityName')), reference(resourceId('Microsoft.App/containerApps', parameters('name')), '2023-04-01-preview', 'full').identity.principalId, reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), '2023-01-31').principalId))]" + }, + "imageName": { + "type": "string", + "value": "[parameters('imageName')]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "serviceBind": { + "type": "object", + "value": "[if(not(empty(parameters('serviceType'))), createObject('serviceId', resourceId('Microsoft.App/containerApps', parameters('name')), 'name', parameters('name')), createObject())]" + }, + "uri": { + "type": "string", + "value": "[if(parameters('ingressEnabled'), format('https://{0}', reference(resourceId('Microsoft.App/containerApps', parameters('name')), '2023-04-01-preview').configuration.ingress.fqdn), '')]" + } + } + } + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-update', deployment().name)), '2022-09-01').outputs.defaultDomain.value]" + }, + "imageName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-update', deployment().name)), '2022-09-01').outputs.imageName.value]" + }, + "name": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-update', deployment().name)), '2022-09-01').outputs.name.value]" + }, + "uri": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-update', deployment().name)), '2022-09-01').outputs.uri.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]", + "[resourceId('Microsoft.Resources/deployments', 'api-keyvault-access')]" + ] + } + ], + "outputs": { + "SERVICE_API_IDENTITY_PRINCIPAL_ID": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')), '2023-01-31').principalId]" + }, + "SERVICE_API_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-app', parameters('serviceName'))), '2022-09-01').outputs.name.value]" + }, + "SERVICE_API_URI": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-app', parameters('serviceName'))), '2022-09-01').outputs.uri.value]" + }, + "SERVICE_API_IMAGE_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-app', parameters('serviceName'))), '2022-09-01').outputs.imageName.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'container-apps')]", + "[resourceId('Microsoft.Resources/deployments', 'keyvault')]", + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cosmos", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "accountName": "[if(not(empty(parameters('cosmosAccountName'))), createObject('value', parameters('cosmosAccountName')), createObject('value', format('{0}{1}', variables('abbrs').documentDBDatabaseAccounts, variables('resourceToken'))))]", + "databaseName": { + "value": "[parameters('cosmosDatabaseName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "keyVaultName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyvault'), '2022-09-01').outputs.name.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "5730728686647632614" + } + }, + "parameters": { + "accountName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "collections": { + "type": "array", + "defaultValue": [ + { + "name": "TodoList", + "id": "TodoList", + "shardKey": "Hash", + "indexKey": "_id" + }, + { + "name": "TodoItem", + "id": "TodoItem", + "shardKey": "Hash", + "indexKey": "_id" + } + ] + }, + "databaseName": { + "type": "string", + "defaultValue": "" + }, + "keyVaultName": { + "type": "string" + } + }, + "variables": { + "defaultDatabaseName": "Todo", + "actualDatabaseName": "[if(not(empty(parameters('databaseName'))), parameters('databaseName'), variables('defaultDatabaseName'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cosmos-mongo", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "accountName": { + "value": "[parameters('accountName')]" + }, + "databaseName": { + "value": "[variables('actualDatabaseName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "collections": { + "value": "[parameters('collections')]" + }, + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "14549161001187918251" + }, + "description": "Creates an Azure Cosmos DB for MongoDB account with a database." + }, + "parameters": { + "accountName": { + "type": "string" + }, + "databaseName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "collections": { + "type": "array", + "defaultValue": [] + }, + "connectionStringKey": { + "type": "string", + "defaultValue": "AZURE-COSMOS-CONNECTION-STRING" + }, + "keyVaultName": { + "type": "string" + } + }, + "resources": [ + { + "copy": { + "name": "list", + "count": "[length(parameters('collections'))]" + }, + "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections", + "apiVersion": "2022-08-15", + "name": "[format('{0}/{1}/{2}', split(format('{0}/{1}', parameters('accountName'), parameters('databaseName')), '/')[0], split(format('{0}/{1}', parameters('accountName'), parameters('databaseName')), '/')[1], parameters('collections')[copyIndex()].name)]", + "properties": { + "resource": { + "id": "[parameters('collections')[copyIndex()].id]", + "shardKey": { + "_id": "[parameters('collections')[copyIndex()].shardKey]" + }, + "indexes": [ + { + "key": { + "keys": [ + "[parameters('collections')[copyIndex()].indexKey]" + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases', split(format('{0}/{1}', parameters('accountName'), parameters('databaseName')), '/')[0], split(format('{0}/{1}', parameters('accountName'), parameters('databaseName')), '/')[1])]" + ] + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases", + "apiVersion": "2022-08-15", + "name": "[format('{0}/{1}', parameters('accountName'), parameters('databaseName'))]", + "tags": "[parameters('tags')]", + "properties": { + "resource": { + "id": "[parameters('databaseName')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'cosmos-mongo-account')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cosmos-mongo-account", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('accountName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "connectionStringKey": { + "value": "[parameters('connectionStringKey')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8317058180807592714" + }, + "description": "Creates an Azure Cosmos DB for MongoDB account." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "keyVaultName": { + "type": "string" + }, + "connectionStringKey": { + "type": "string", + "defaultValue": "AZURE-COSMOS-CONNECTION-STRING" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cosmos-account", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "connectionStringKey": { + "value": "[parameters('connectionStringKey')]" + }, + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "kind": { + "value": "MongoDB" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "13614361263700788271" + }, + "description": "Creates an Azure Cosmos DB account." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "connectionStringKey": { + "type": "string", + "defaultValue": "AZURE-COSMOS-CONNECTION-STRING" + }, + "keyVaultName": { + "type": "string" + }, + "kind": { + "type": "string", + "allowedValues": [ + "GlobalDocumentDB", + "MongoDB", + "Parse" + ] + } + }, + "resources": [ + { + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2022-08-15", + "name": "[parameters('name')]", + "kind": "[parameters('kind')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "consistencyPolicy": { + "defaultConsistencyLevel": "Session" + }, + "locations": [ + { + "locationName": "[parameters('location')]", + "failoverPriority": 0, + "isZoneRedundant": false + } + ], + "databaseAccountOfferType": "Standard", + "enableAutomaticFailover": false, + "enableMultipleWriteLocations": false, + "apiProperties": "[if(equals(parameters('kind'), 'MongoDB'), createObject('serverVersion', '4.2'), createObject())]", + "capabilities": [ + { + "name": "EnableServerless" + } + ] + } + }, + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('connectionStringKey'))]", + "properties": { + "value": "[listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2022-08-15').connectionStrings[0].connectionString]" + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionStringKey": { + "type": "string", + "value": "[parameters('connectionStringKey')]" + }, + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2022-08-15').documentEndpoint]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + } + ], + "outputs": { + "connectionStringKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-account'), '2022-09-01').outputs.connectionStringKey.value]" + }, + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-account'), '2022-09-01').outputs.endpoint.value]" + }, + "id": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-account'), '2022-09-01').outputs.id.value]" + } + } + } + } + } + ], + "outputs": { + "connectionStringKey": { + "type": "string", + "value": "[parameters('connectionStringKey')]" + }, + "databaseName": { + "type": "string", + "value": "[parameters('databaseName')]" + }, + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-mongo-account'), '2022-09-01').outputs.endpoint.value]" + } + } + } + } + } + ], + "outputs": { + "connectionStringKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-mongo'), '2022-09-01').outputs.connectionStringKey.value]" + }, + "databaseName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-mongo'), '2022-09-01').outputs.databaseName.value]" + }, + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-mongo'), '2022-09-01').outputs.endpoint.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'keyvault')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "keyvault", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(not(empty(parameters('keyVaultName'))), createObject('value', parameters('keyVaultName')), createObject('value', format('{0}{1}', variables('abbrs').keyVaultVaults, variables('resourceToken'))))]", + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "principalId": { + "value": "[parameters('principalId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "17948623451174129396" + }, + "description": "Creates an Azure Key Vault." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "principalId": { + "type": "string", + "defaultValue": "" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "tenantId": "[subscription().tenantId]", + "sku": { + "family": "A", + "name": "standard" + }, + "accessPolicies": "[if(not(empty(parameters('principalId'))), createArray(createObject('objectId', parameters('principalId'), 'permissions', createObject('secrets', createArray('get', 'list')), 'tenantId', subscription().tenantId)), createArray())]" + } + } + ], + "outputs": { + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '2022-07-01').vaultUri]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "monitoring", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "logAnalyticsName": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', parameters('logAnalyticsName')), createObject('value', format('{0}{1}', variables('abbrs').operationalInsightsWorkspaces, variables('resourceToken'))))]", + "applicationInsightsName": "[if(not(empty(parameters('applicationInsightsName'))), createObject('value', parameters('applicationInsightsName')), createObject('value', format('{0}{1}', variables('abbrs').insightsComponents, variables('resourceToken'))))]", + "applicationInsightsDashboardName": "[if(not(empty(parameters('applicationInsightsDashboardName'))), createObject('value', parameters('applicationInsightsDashboardName')), createObject('value', format('{0}{1}', variables('abbrs').portalDashboards, variables('resourceToken'))))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10041669792322197047" + }, + "description": "Creates an Application Insights instance and a Log Analytics workspace." + }, + "parameters": { + "logAnalyticsName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "loganalytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('logAnalyticsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "9622176141085970536" + }, + "description": "Creates a Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-12-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": 30, + "features": { + "searchVersion": 1 + }, + "sku": { + "name": "PerGB2018" + } + } + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "applicationinsights", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('applicationInsightsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "dashboardName": { + "value": "[parameters('applicationInsightsDashboardName')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1335628967363670282" + }, + "description": "Creates an Application Insights instance based on an existing Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "dashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "logAnalyticsWorkspaceId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]" + } + }, + { + "condition": "[not(empty(parameters('dashboardName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "application-insights-dashboard", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('dashboardName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "applicationInsightsName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "2145880658446193205" + }, + "description": "Creates a dashboard for an Application Insights instance." + }, + "parameters": { + "name": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Portal/dashboards", + "apiVersion": "2020-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "lenses": [ + { + "order": 0, + "parts": [ + { + "position": { + "x": 0, + "y": 0, + "colSpan": 2, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "id", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart", + "asset": { + "idInputName": "id", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "overview" + } + }, + { + "position": { + "x": 2, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "ProactiveDetection" + } + }, + { + "position": { + "x": 3, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + } + ], + "type": "Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:20:33.345Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 5, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-08T18:47:35.237Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "ConfigurationId", + "value": "78ce933e-e864-4b05-a27b-71fd55a6afad" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AppMapButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 0, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Usage", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 3, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:22:35.782Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + } + ], + "type": "Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Reliability", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 7, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:42:40.072Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "failures" + } + }, + { + "position": { + "x": 8, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Responsiveness\r\n", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 11, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:43:37.804Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "2a8ede4f-2bee-4b9c-aed9-2db0e8a01865", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "performance" + } + }, + { + "position": { + "x": 12, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Browser", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 15, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "MetricsExplorerJsonDefinitionId", + "value": "BrowserPerformanceTimelineMetrics" + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "createdTime": "2018-05-08T12:16:27.534Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "CurrentFilter", + "value": { + "eventTypes": [ + 4, + 1, + 3, + 5, + 2, + 6, + 13 + ], + "typeFacets": {}, + "isPermissive": false + } + }, + { + "name": "id", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "browser" + } + }, + { + "position": { + "x": 0, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "sessions/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Sessions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "users/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Users", + "color": "#7E58FF" + } + } + ], + "title": "Unique sessions and users", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "segmentationUsers" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Failed requests", + "color": "#EC008C" + } + } + ], + "title": "Failed requests", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "failures" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/duration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server response time", + "color": "#00BCF2" + } + } + ], + "title": "Server response time", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "performance" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/networkDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Page load network connect time", + "color": "#7E58FF" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/processingDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Client processing time", + "color": "#44F1C8" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/sendDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Send request time", + "color": "#EB9371" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/receiveDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Receiving response time", + "color": "#0672F1" + } + } + ], + "title": "Average page load time breakdown", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/availabilityPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability", + "color": "#47BDF5" + } + } + ], + "title": "Average availability", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "availability" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/server", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server exceptions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "dependencies/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Dependency failures", + "color": "#7E58FF" + } + } + ], + "title": "Server exceptions and Dependency failures", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processorCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Processor time", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process CPU", + "color": "#7E58FF" + } + } + ], + "title": "Average processor and process CPU utilization", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/browser", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Browser exceptions", + "color": "#47BDF5" + } + } + ], + "title": "Browser exceptions", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/count", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability test results count", + "color": "#47BDF5" + } + } + ], + "title": "Availability test results count", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processIOBytesPerSecond", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process IO rate", + "color": "#47BDF5" + } + } + ], + "title": "Average process I/O rate", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/memoryAvailableBytes", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Available memory", + "color": "#47BDF5" + } + } + ], + "title": "Average available memory", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + } + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').ConnectionString]" + }, + "instrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').InstrumentationKey]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'loganalytics')]" + ] + } + ], + "outputs": { + "applicationInsightsConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.connectionString.value]" + }, + "applicationInsightsInstrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "applicationInsightsName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.name.value]" + } + } + } + } + }, + { + "condition": "[parameters('useAPIM')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "apim-deployment", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(not(empty(parameters('apimServiceName'))), createObject('value', parameters('apimServiceName')), createObject('value', format('{0}{1}', variables('abbrs').apiManagementService, variables('resourceToken'))))]", + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "applicationInsightsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "3036576769636454145" + }, + "description": "Creates an Azure API Management instance." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "publisherEmail": { + "type": "string", + "defaultValue": "noreply@microsoft.com", + "minLength": 1, + "metadata": { + "description": "The email address of the owner of the service" + } + }, + "publisherName": { + "type": "string", + "defaultValue": "n/a", + "minLength": 1, + "metadata": { + "description": "The name of the owner of the service" + } + }, + "sku": { + "type": "string", + "defaultValue": "Consumption", + "allowedValues": [ + "Consumption", + "Developer", + "Standard", + "Premium" + ], + "metadata": { + "description": "The pricing tier of this API Management service" + } + }, + "skuCount": { + "type": "int", + "defaultValue": 0, + "allowedValues": [ + 0, + 1, + 2 + ], + "metadata": { + "description": "The instance size of this API Management service." + } + }, + "applicationInsightsName": { + "type": "string", + "metadata": { + "description": "Azure Application Insights Name" + } + } + }, + "resources": [ + { + "type": "Microsoft.ApiManagement/service", + "apiVersion": "2021-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), createObject('azd-service-name', parameters('name')))]", + "sku": { + "name": "[parameters('sku')]", + "capacity": "[if(equals(parameters('sku'), 'Consumption'), 0, if(equals(parameters('sku'), 'Developer'), 1, parameters('skuCount')))]" + }, + "properties": { + "publisherEmail": "[parameters('publisherEmail')]", + "publisherName": "[parameters('publisherName')]", + "customProperties": "[if(equals(parameters('sku'), 'Consumption'), createObject(), createObject('Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_GCM_SHA256', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA256', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA256', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TripleDes168', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Ssl30', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11', 'false', 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30', 'false'))]" + } + }, + { + "condition": "[not(empty(parameters('applicationInsightsName')))]", + "type": "Microsoft.ApiManagement/service/loggers", + "apiVersion": "2021-12-01-preview", + "name": "[format('{0}/{1}', parameters('name'), 'app-insights-logger')]", + "properties": { + "credentials": { + "instrumentationKey": "[reference(resourceId('Microsoft.Insights/components', parameters('applicationInsightsName')), '2020-02-02').InstrumentationKey]" + }, + "description": "Logger to Azure Application Insights", + "isBuffered": false, + "loggerType": "applicationInsights", + "resourceId": "[resourceId('Microsoft.Insights/components', parameters('applicationInsightsName'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ApiManagement/service', parameters('name'))]" + ] + } + ], + "outputs": { + "apimServiceName": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "condition": "[parameters('useAPIM')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "apim-api-deployment", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(parameters('useAPIM'), createObject('value', reference(resourceId('Microsoft.Resources/deployments', 'apim-deployment'), '2022-09-01').outputs.apimServiceName.value), createObject('value', ''))]", + "apiName": { + "value": "todo-api" + }, + "apiDisplayName": { + "value": "Simple Todo API" + }, + "apiDescription": { + "value": "This is a simple Todo API" + }, + "apiPath": { + "value": "todo" + }, + "webFrontendUrl": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'web'), '2022-09-01').outputs.SERVICE_WEB_URI.value]" + }, + "apiBackendUrl": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'api'), '2022-09-01').outputs.SERVICE_API_URI.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "6615097664318461925" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "apiName": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Resource name to uniquely identify this API within the API Management service instance" + } + }, + "apiDisplayName": { + "type": "string", + "minLength": 1, + "maxLength": 300, + "metadata": { + "description": "The Display Name of the API" + } + }, + "apiDescription": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Description of the API. May include HTML formatting tags." + } + }, + "apiPath": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API." + } + }, + "webFrontendUrl": { + "type": "string", + "metadata": { + "description": "Absolute URL of the web frontend" + } + }, + "apiBackendUrl": { + "type": "string", + "metadata": { + "description": "Absolute URL of the backend service implementing this API." + } + }, + "apiAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource name for backend Web App or Function App" + } + } + }, + "variables": { + "$fxv#0": "\n\n \n \n \n \n \n {origin}\n \n \n PUT\n GET\n POST\n DELETE\n PATCH\n \n \n
*
\n
\n \n
*
\n
\n
\n \n \n \n \n \n \n Call to the @(context.Api.Name)\n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n = 200 && context.Response.StatusCode < 300)\">\n \n \n \n \n \n \n \n = 400 && context.Response.StatusCode < 600)\">\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Failed to process the @(context.Api.Name)\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n An unexpected error has occurred.\n \n \n
\n", + "$fxv#1": "openapi: 3.0.0\ninfo:\n description: Simple Todo API\n version: 3.0.0\n title: Simple Todo API\n contact:\n email: azdevteam@microsoft.com\n\ncomponents:\n schemas:\n TodoItem:\n type: object\n required:\n - listId\n - name\n - description\n description: A task that needs to be completed\n properties:\n id:\n type: string\n listId:\n type: string\n name:\n type: string\n description:\n type: string\n state:\n $ref: \"#/components/schemas/TodoState\"\n dueDate:\n type: string\n format: date-time\n completedDate:\n type: string\n format: date-time\n TodoList:\n type: object\n required:\n - name\n properties:\n id:\n type: string\n name:\n type: string\n description:\n type: string\n description: \" A list of related Todo items\"\n TodoState:\n type: string\n enum:\n - todo\n - inprogress\n - done\n parameters:\n listId:\n in: path\n required: true\n name: listId\n description: The Todo list unique identifier\n schema:\n type: string\n itemId:\n in: path\n required: true\n name: itemId\n description: The Todo item unique identifier\n schema:\n type: string\n state:\n in: path\n required: true\n name: state\n description: The Todo item state\n schema:\n $ref: \"#/components/schemas/TodoState\"\n top:\n in: query\n required: false\n name: top\n description: The max number of items to returns in a result\n schema:\n type: number\n default: 20\n skip:\n in: query\n required: false\n name: skip\n description: The number of items to skip within the results\n schema:\n type: number\n default: 0\n\n requestBodies:\n TodoList:\n description: The Todo List\n content:\n application/json:\n schema:\n $ref: \"#/components/schemas/TodoList\"\n TodoItem:\n description: The Todo Item\n content:\n application/json:\n schema:\n $ref: \"#/components/schemas/TodoItem\"\n\n responses:\n TodoList:\n description: A Todo list result\n content:\n application/json:\n schema:\n $ref: \"#/components/schemas/TodoList\"\n TodoListArray:\n description: An array of Todo lists\n content:\n application/json:\n schema:\n type: array\n items:\n $ref: \"#/components/schemas/TodoList\"\n TodoItem:\n description: A Todo item result\n content:\n application/json:\n schema:\n $ref: \"#/components/schemas/TodoItem\"\n TodoItemArray:\n description: An array of Todo items\n content:\n application/json:\n schema:\n type: array\n items:\n $ref: \"#/components/schemas/TodoItem\"\n\npaths:\n /lists:\n get:\n operationId: GetLists\n summary: Gets an array of Todo lists\n tags:\n - Lists\n parameters:\n - $ref: \"#/components/parameters/top\"\n - $ref: \"#/components/parameters/skip\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoListArray\"\n post:\n operationId: CreateList\n summary: Creates a new Todo list\n tags:\n - Lists\n requestBody:\n $ref: \"#/components/requestBodies/TodoList\"\n responses:\n 201:\n $ref: \"#/components/responses/TodoList\"\n 400:\n description: Invalid request schema\n /lists/{listId}:\n get:\n operationId: GetListById\n summary: Gets a Todo list by unique identifier\n tags:\n - Lists\n parameters:\n - $ref: \"#/components/parameters/listId\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoList\"\n 404:\n description: Todo list not found\n put:\n operationId: UpdateListById\n summary: Updates a Todo list by unique identifier\n tags:\n - Lists\n requestBody:\n $ref: \"#/components/requestBodies/TodoList\"\n parameters:\n - $ref: \"#/components/parameters/listId\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoList\"\n 404:\n description: Todo list not found\n 400:\n description: Todo list is invalid\n delete:\n operationId: DeleteListById\n summary: Deletes a Todo list by unique identifier\n tags:\n - Lists\n parameters:\n - $ref: \"#/components/parameters/listId\"\n responses:\n 204:\n description: Todo list deleted successfully\n 404:\n description: Todo list not found\n /lists/{listId}/items:\n post:\n operationId: CreateItem\n summary: Creates a new Todo item within a list\n tags:\n - Items\n requestBody:\n $ref: \"#/components/requestBodies/TodoItem\"\n parameters:\n - $ref: \"#/components/parameters/listId\"\n responses:\n 201:\n $ref: \"#/components/responses/TodoItem\"\n 404:\n description: Todo list not found\n get:\n operationId: GetItemsByListId\n summary: Gets Todo items within the specified list\n tags:\n - Items\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/top\"\n - $ref: \"#/components/parameters/skip\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoItemArray\"\n 404:\n description: Todo list not found\n /lists/{listId}/items/{itemId}:\n get:\n operationId: GetItemById\n summary: Gets a Todo item by unique identifier\n tags:\n - Items\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/itemId\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoItem\"\n 404:\n description: Todo list or item not found\n put:\n operationId: UpdateItemById\n summary: Updates a Todo item by unique identifier\n tags:\n - Items\n requestBody:\n $ref: \"#/components/requestBodies/TodoItem\"\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/itemId\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoItem\"\n 400:\n description: Todo item is invalid\n 404:\n description: Todo list or item not found\n delete:\n operationId: DeleteItemById\n summary: Deletes a Todo item by unique identifier\n tags:\n - Items\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/itemId\"\n responses:\n 204:\n description: Todo item deleted successfully\n 404:\n description: Todo list or item not found\n /lists/{listId}/items/state/{state}:\n get:\n operationId: GetItemsByListIdAndState\n summary: Gets a list of Todo items of a specific state\n tags:\n - Items\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/state\"\n - $ref: \"#/components/parameters/top\"\n - $ref: \"#/components/parameters/skip\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoItemArray\"\n 404:\n description: Todo list or item not found\n put:\n operationId: UpdateItemsStateByListId\n summary: Changes the state of the specified list items\n tags:\n - Items\n requestBody:\n description: unique identifiers of the Todo items to update\n content:\n application/json:\n schema:\n type: array\n items:\n description: The Todo item unique identifier\n type: string\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/state\"\n responses:\n 204:\n description: Todo items updated\n 400:\n description: Update request is invalid\n", + "apiPolicyContent": "[replace(variables('$fxv#0'), '{origin}', parameters('webFrontendUrl'))]", + "appNameForBicep": "[if(not(empty(parameters('apiAppName'))), parameters('apiAppName'), 'placeholderName')]" + }, + "resources": [ + { + "type": "Microsoft.ApiManagement/service/apis", + "apiVersion": "2021-12-01-preview", + "name": "[format('{0}/{1}', parameters('name'), parameters('apiName'))]", + "properties": { + "description": "[parameters('apiDescription')]", + "displayName": "[parameters('apiDisplayName')]", + "path": "[parameters('apiPath')]", + "protocols": [ + "https" + ], + "subscriptionRequired": false, + "type": "http", + "format": "openapi", + "serviceUrl": "[parameters('apiBackendUrl')]", + "value": "[variables('$fxv#1')]" + } + }, + { + "type": "Microsoft.ApiManagement/service/apis/policies", + "apiVersion": "2021-12-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('name'), parameters('apiName'), 'policy')]", + "properties": { + "format": "rawxml", + "value": "[variables('apiPolicyContent')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ApiManagement/service/apis', parameters('name'), parameters('apiName'))]" + ] + }, + { + "type": "Microsoft.ApiManagement/service/apis/diagnostics", + "apiVersion": "2021-12-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('name'), parameters('apiName'), 'applicationinsights')]", + "properties": { + "alwaysLog": "allErrors", + "backend": { + "request": { + "body": { + "bytes": 1024 + } + }, + "response": { + "body": { + "bytes": 1024 + } + } + }, + "frontend": { + "request": { + "body": { + "bytes": 1024 + } + }, + "response": { + "body": { + "bytes": 1024 + } + } + }, + "httpCorrelationProtocol": "W3C", + "logClientIp": true, + "loggerId": "[resourceId('Microsoft.ApiManagement/service/loggers', parameters('name'), 'app-insights-logger')]", + "metrics": true, + "sampling": { + "percentage": 100, + "samplingType": "fixed" + }, + "verbosity": "verbose" + }, + "dependsOn": [ + "[resourceId('Microsoft.ApiManagement/service/apis', parameters('name'), parameters('apiName'))]" + ] + }, + { + "condition": "[not(empty(parameters('apiAppName')))]", + "type": "Microsoft.Web/sites/config", + "apiVersion": "2022-03-01", + "name": "[format('{0}/web', variables('appNameForBicep'))]", + "kind": "string", + "properties": { + "apiManagementConfig": { + "id": "[format('{0}/apis/{1}', resourceId('Microsoft.ApiManagement/service', parameters('name')), parameters('apiName'))]" + } + } + } + ], + "outputs": { + "SERVICE_API_URI": { + "type": "string", + "value": "[format('{0}/{1}', reference(resourceId('Microsoft.ApiManagement/service', parameters('name')), '2021-08-01').gatewayUrl, parameters('apiPath'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'api')]", + "[resourceId('Microsoft.Resources/deployments', 'apim-deployment')]", + "[resourceId('Microsoft.Resources/deployments', 'web')]" + ] + } + ], + "outputs": { + "AZURE_COSMOS_CONNECTION_STRING_KEY": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos'), '2022-09-01').outputs.connectionStringKey.value]" + }, + "AZURE_COSMOS_DATABASE_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos'), '2022-09-01').outputs.databaseName.value]" + }, + "API_CORS_ACA_URL": { + "type": "string", + "value": "[format('https://{0}.{1}', variables('apiContainerAppNameOrDefault'), reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.defaultDomain.value)]" + }, + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "APPLICATIONINSIGHTS_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + }, + "AZURE_CONTAINER_ENVIRONMENT_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.environmentName.value]" + }, + "AZURE_CONTAINER_REGISTRY_ENDPOINT": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.registryLoginServer.value]" + }, + "AZURE_CONTAINER_REGISTRY_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.registryName.value]" + }, + "AZURE_KEY_VAULT_ENDPOINT": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyvault'), '2022-09-01').outputs.endpoint.value]" + }, + "AZURE_KEY_VAULT_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyvault'), '2022-09-01').outputs.name.value]" + }, + "AZURE_LOCATION": { + "type": "string", + "value": "[parameters('location')]" + }, + "AZURE_TENANT_ID": { + "type": "string", + "value": "[tenant().tenantId]" + }, + "REACT_APP_API_BASE_URL": { + "type": "string", + "value": "[if(parameters('useAPIM'), reference(resourceId('Microsoft.Resources/deployments', 'apim-api-deployment'), '2022-09-01').outputs.SERVICE_API_URI.value, reference(resourceId('Microsoft.Resources/deployments', 'api'), '2022-09-01').outputs.SERVICE_API_URI.value)]" + }, + "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "REACT_APP_WEB_BASE_URL": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'web'), '2022-09-01').outputs.SERVICE_WEB_URI.value]" + }, + "SERVICE_API_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'api'), '2022-09-01').outputs.SERVICE_API_NAME.value]" + }, + "SERVICE_WEB_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'web'), '2022-09-01').outputs.SERVICE_WEB_NAME.value]" + }, + "USE_APIM": { + "type": "bool", + "value": "[parameters('useAPIM')]" + }, + "SERVICE_API_ENDPOINTS": { + "type": "array", + "value": "[if(parameters('useAPIM'), createArray(reference(resourceId('Microsoft.Resources/deployments', 'apim-api-deployment'), '2022-09-01').outputs.SERVICE_API_URI.value, reference(resourceId('Microsoft.Resources/deployments', 'api'), '2022-09-01').outputs.SERVICE_API_URI.value), createArray())]" + } + } +} \ No newline at end of file diff --git a/Environments/App-Base-WebApp-ACA/core/ai/cognitiveservices.bicep b/Environments/App-Base-WebApp-ACA/core/ai/cognitiveservices.bicep new file mode 100644 index 00000000..1bf5666b --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/ai/cognitiveservices.bicep @@ -0,0 +1,53 @@ +metadata description = 'Creates an Azure Cognitive Services instance.' +param name string +param location string = resourceGroup().location +param tags object = {} +@description('The custom subdomain name used to access the API. Defaults to the value of the name parameter.') +param customSubDomainName string = name +param deployments array = [] +param kind string = 'OpenAI' + +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { + name: 'S0' +} + +param allowedIpRules array = [] +param networkAcls object = empty(allowedIpRules) ? { + defaultAction: 'Allow' +} : { + ipRules: allowedIpRules + defaultAction: 'Deny' +} + +resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + customSubDomainName: customSubDomainName + publicNetworkAccess: publicNetworkAccess + networkAcls: networkAcls + } + sku: sku +} + +@batchSize(1) +resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: { + parent: account + name: deployment.name + properties: { + model: deployment.model + raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null + } + sku: contains(deployment, 'sku') ? deployment.sku : { + name: 'Standard' + capacity: 20 + } +}] + +output endpoint string = account.properties.endpoint +output id string = account.id +output name string = account.name diff --git a/Environments/App-Base-WebApp-ACA/core/database/cosmos/cosmos-account.bicep b/Environments/App-Base-WebApp-ACA/core/database/cosmos/cosmos-account.bicep new file mode 100644 index 00000000..6f8747f5 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/database/cosmos/cosmos-account.bicep @@ -0,0 +1,49 @@ +metadata description = 'Creates an Azure Cosmos DB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +@allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) +param kind string + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { + name: name + kind: kind + location: location + tags: tags + properties: { + consistencyPolicy: { defaultConsistencyLevel: 'Session' } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + databaseAccountOfferType: 'Standard' + enableAutomaticFailover: false + enableMultipleWriteLocations: false + apiProperties: (kind == 'MongoDB') ? { serverVersion: '4.2' } : {} + capabilities: [ { name: 'EnableServerless' } ] + } +} + +resource cosmosConnectionString 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: cosmos.listConnectionStrings().connectionStrings[0].connectionString + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output connectionStringKey string = connectionStringKey +output endpoint string = cosmos.properties.documentEndpoint +output id string = cosmos.id +output name string = cosmos.name diff --git a/Environments/App-Base-WebApp-ACA/core/database/cosmos/mongo/cosmos-mongo-account.bicep b/Environments/App-Base-WebApp-ACA/core/database/cosmos/mongo/cosmos-mongo-account.bicep new file mode 100644 index 00000000..4aafbf38 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/database/cosmos/mongo/cosmos-mongo-account.bicep @@ -0,0 +1,23 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + connectionStringKey: connectionStringKey + keyVaultName: keyVaultName + kind: 'MongoDB' + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id diff --git a/Environments/App-Base-WebApp-ACA/core/database/cosmos/mongo/cosmos-mongo-db.bicep b/Environments/App-Base-WebApp-ACA/core/database/cosmos/mongo/cosmos-mongo-db.bicep new file mode 100644 index 00000000..2a670578 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/database/cosmos/mongo/cosmos-mongo-db.bicep @@ -0,0 +1,47 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [] +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +module cosmos 'cosmos-mongo-account.bicep' = { + name: 'cosmos-mongo-account' + params: { + name: accountName + location: location + keyVaultName: keyVaultName + tags: tags + connectionStringKey: connectionStringKey + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2022-08-15' = { + name: '${accountName}/${databaseName}' + tags: tags + properties: { + resource: { id: databaseName } + } + + resource list 'collections' = [for collection in collections: { + name: collection.name + properties: { + resource: { + id: collection.id + shardKey: { _id: collection.shardKey } + indexes: [ { key: { keys: [ collection.indexKey ] } } ] + } + } + }] + + dependsOn: [ + cosmos + ] +} + +output connectionStringKey string = connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-account.bicep b/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-account.bicep new file mode 100644 index 00000000..8431135e --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-account.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + tags: tags + keyVaultName: keyVaultName + kind: 'GlobalDocumentDB' + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id +output name string = cosmos.outputs.name diff --git a/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-db.bicep b/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-db.bicep new file mode 100644 index 00000000..265880dc --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-db.bicep @@ -0,0 +1,74 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param containers array = [] +param keyVaultName string +param principalIds array = [] + +module cosmos 'cosmos-sql-account.bicep' = { + name: 'cosmos-sql-account' + params: { + name: accountName + location: location + tags: tags + keyVaultName: keyVaultName + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = { + name: '${accountName}/${databaseName}' + properties: { + resource: { id: databaseName } + } + + resource list 'containers' = [for container in containers: { + name: container.name + properties: { + resource: { + id: container.id + partitionKey: { paths: [ container.partitionKey ] } + } + options: {} + } + }] + + dependsOn: [ + cosmos + ] +} + +module roleDefinition 'cosmos-sql-role-def.bicep' = { + name: 'cosmos-sql-role-definition' + params: { + accountName: accountName + } + dependsOn: [ + cosmos + database + ] +} + +// We need batchSize(1) here because sql role assignments have to be done sequentially +@batchSize(1) +module userRole 'cosmos-sql-role-assign.bicep' = [for principalId in principalIds: if (!empty(principalId)) { + name: 'cosmos-sql-user-role-${uniqueString(principalId)}' + params: { + accountName: accountName + roleDefinitionId: roleDefinition.outputs.id + principalId: principalId + } + dependsOn: [ + cosmos + database + ] +}] + +output accountId string = cosmos.outputs.id +output accountName string = cosmos.outputs.name +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint +output roleDefinitionId string = roleDefinition.outputs.id diff --git a/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-role-assign.bicep b/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-role-assign.bicep new file mode 100644 index 00000000..3949efef --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-role-assign.bicep @@ -0,0 +1,19 @@ +metadata description = 'Creates a SQL role assignment under an Azure Cosmos DB account.' +param accountName string + +param roleDefinitionId string +param principalId string = '' + +resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { + parent: cosmos + name: guid(roleDefinitionId, principalId, cosmos.id) + properties: { + principalId: principalId + roleDefinitionId: roleDefinitionId + scope: cosmos.id + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} diff --git a/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-role-def.bicep b/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-role-def.bicep new file mode 100644 index 00000000..778d6dc4 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/database/cosmos/sql/cosmos-sql-role-def.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates a SQL role definition under an Azure Cosmos DB account.' +param accountName string + +resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2022-08-15' = { + parent: cosmos + name: guid(cosmos.id, accountName, 'sql-role') + properties: { + assignableScopes: [ + cosmos.id + ] + permissions: [ + { + dataActions: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + ] + notDataActions: [] + } + ] + roleName: 'Reader Writer' + type: 'CustomRole' + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} + +output id string = roleDefinition.id diff --git a/Environments/App-Base-WebApp-ACA/core/database/postgresql/flexibleserver.bicep b/Environments/App-Base-WebApp-ACA/core/database/postgresql/flexibleserver.bicep new file mode 100644 index 00000000..7e26b1a8 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/database/postgresql/flexibleserver.bicep @@ -0,0 +1,65 @@ +metadata description = 'Creates an Azure Database for PostgreSQL - Flexible Server.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object +param storage object +param administratorLogin string +@secure() +param administratorLoginPassword string +param databaseNames array = [] +param allowAzureIPsFirewall bool = false +param allowAllIPsFirewall bool = false +param allowedSingleIPs array = [] + +// PostgreSQL version +param version string + +// Latest official version 2022-12-01 does not have Bicep types available +resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = { + location: location + tags: tags + name: name + sku: sku + properties: { + version: version + administratorLogin: administratorLogin + administratorLoginPassword: administratorLoginPassword + storage: storage + highAvailability: { + mode: 'Disabled' + } + } + + resource database 'databases' = [for name in databaseNames: { + name: name + }] + + resource firewall_all 'firewallRules' = if (allowAllIPsFirewall) { + name: 'allow-all-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '255.255.255.255' + } + } + + resource firewall_azure 'firewallRules' = if (allowAzureIPsFirewall) { + name: 'allow-all-azure-internal-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '0.0.0.0' + } + } + + resource firewall_single 'firewallRules' = [for ip in allowedSingleIPs: { + name: 'allow-single-${replace(ip, '.', '')}' + properties: { + startIpAddress: ip + endIpAddress: ip + } + }] + +} + +output POSTGRES_DOMAIN_NAME string = postgresServer.properties.fullyQualifiedDomainName diff --git a/Environments/App-Base-WebApp-ACA/core/database/sqlserver/sqlserver.bicep b/Environments/App-Base-WebApp-ACA/core/database/sqlserver/sqlserver.bicep new file mode 100644 index 00000000..84f2cc2c --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/database/sqlserver/sqlserver.bicep @@ -0,0 +1,130 @@ +metadata description = 'Creates an Azure SQL Server instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param appUser string = 'appUser' +param databaseName string +param keyVaultName string +param sqlAdmin string = 'sqlAdmin' +param connectionStringKey string = 'AZURE-SQL-CONNECTION-STRING' + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name + location: location + tags: tags + properties: { + version: '12.0' + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + administratorLogin: sqlAdmin + administratorLoginPassword: sqlAdminPassword + } + + resource database 'databases' = { + name: databaseName + location: location + } + + resource firewall 'firewallRules' = { + name: 'Azure Services' + properties: { + // Allow all clients + // Note: range [0.0.0.0-0.0.0.0] means "allow all Azure-hosted clients only". + // This is not sufficient, because we also want to allow direct access from developer machine, for debugging purposes. + startIpAddress: '0.0.0.1' + endIpAddress: '255.255.255.254' + } + } +} + +resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: '${name}-deployment-script' + location: location + kind: 'AzureCLI' + properties: { + azCliVersion: '2.37.0' + retentionInterval: 'PT1H' // Retain the script resource for 1 hour after it ends running + timeout: 'PT5M' // Five minutes + cleanupPreference: 'OnSuccess' + environmentVariables: [ + { + name: 'APPUSERNAME' + value: appUser + } + { + name: 'APPUSERPASSWORD' + secureValue: appUserPassword + } + { + name: 'DBNAME' + value: databaseName + } + { + name: 'DBSERVER' + value: sqlServer.properties.fullyQualifiedDomainName + } + { + name: 'SQLCMDPASSWORD' + secureValue: sqlAdminPassword + } + { + name: 'SQLADMIN' + value: sqlAdmin + } + ] + + scriptContent: ''' +wget https://github.com/microsoft/go-sqlcmd/releases/download/v0.8.1/sqlcmd-v0.8.1-linux-x64.tar.bz2 +tar x -f sqlcmd-v0.8.1-linux-x64.tar.bz2 -C . + +cat < ./initDb.sql +drop user if exists ${APPUSERNAME} +go +create user ${APPUSERNAME} with password = '${APPUSERPASSWORD}' +go +alter role db_owner add member ${APPUSERNAME} +go +SCRIPT_END + +./sqlcmd -S ${DBSERVER} -d ${DBNAME} -U ${SQLADMIN} -i ./initDb.sql + ''' + } +} + +resource sqlAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'sqlAdminPassword' + properties: { + value: sqlAdminPassword + } +} + +resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'appUserPassword' + properties: { + value: appUserPassword + } +} + +resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: '${connectionString}; Password=${appUserPassword}' + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output connectionStringKey string = connectionStringKey +output databaseName string = sqlServer::database.name diff --git a/Environments/App-Base-WebApp-ACA/core/gateway/apim.bicep b/Environments/App-Base-WebApp-ACA/core/gateway/apim.bicep new file mode 100644 index 00000000..be7464f0 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/gateway/apim.bicep @@ -0,0 +1,79 @@ +metadata description = 'Creates an Azure API Management instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The email address of the owner of the service') +@minLength(1) +param publisherEmail string = 'noreply@microsoft.com' + +@description('The name of the owner of the service') +@minLength(1) +param publisherName string = 'n/a' + +@description('The pricing tier of this API Management service') +@allowed([ + 'Consumption' + 'Developer' + 'Standard' + 'Premium' +]) +param sku string = 'Consumption' + +@description('The instance size of this API Management service.') +@allowed([ 0, 1, 2 ]) +param skuCount int = 0 + +@description('Azure Application Insights Name') +param applicationInsightsName string + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': name }) + sku: { + name: sku + capacity: (sku == 'Consumption') ? 0 : ((sku == 'Developer') ? 1 : skuCount) + } + properties: { + publisherEmail: publisherEmail + publisherName: publisherName + // Custom properties are not supported for Consumption SKU + customProperties: sku == 'Consumption' ? {} : { + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_GCM_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TripleDes168': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Ssl30': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30': 'false' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' = if (!empty(applicationInsightsName)) { + name: 'app-insights-logger' + parent: apimService + properties: { + credentials: { + instrumentationKey: applicationInsights.properties.InstrumentationKey + } + description: 'Logger to Azure Application Insights' + isBuffered: false + loggerType: 'applicationInsights' + resourceId: applicationInsights.id + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output apimServiceName string = apimService.name diff --git a/Environments/App-Base-WebApp-ACA/core/host/aks-agent-pool.bicep b/Environments/App-Base-WebApp-ACA/core/host/aks-agent-pool.bicep new file mode 100644 index 00000000..9c764358 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/aks-agent-pool.bicep @@ -0,0 +1,18 @@ +metadata description = 'Adds an agent pool to an Azure Kubernetes Service (AKS) cluster.' +param clusterName string + +@description('The agent pool name') +param name string + +@description('The agent pool configuration') +param config object + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} + +resource nodePool 'Microsoft.ContainerService/managedClusters/agentPools@2023-10-02-preview' = { + parent: aksCluster + name: name + properties: config +} diff --git a/Environments/App-Base-WebApp-ACA/core/host/aks-managed-cluster.bicep b/Environments/App-Base-WebApp-ACA/core/host/aks-managed-cluster.bicep new file mode 100644 index 00000000..de562a66 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/aks-managed-cluster.bicep @@ -0,0 +1,140 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// AAD Integration +@description('Enable Azure Active Directory integration') +param enableAad bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('Configuration of AKS add-ons') +param addOns object = {} + +@description('The log analytics workspace id used for logging & monitoring') +param workspaceId string = '' + +@description('The node pool configuration for the System agent pool') +param systemPoolConfig object + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +resource aks 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' = { + name: name + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + sku: { + name: 'Base' + tier: sku + } + properties: { + nodeResourceGroup: !empty(nodeResourceGroupName) ? nodeResourceGroupName : 'rg-mc-${name}' + kubernetesVersion: kubernetesVersion + dnsPrefix: empty(dnsPrefix) ? '${name}-dns' : dnsPrefix + enableRBAC: enableRbac + aadProfile: enableAad ? { + managed: true + enableAzureRBAC: enableAzureRbac + tenantID: aadTenantId + } : null + agentPoolProfiles: [ + systemPoolConfig + ] + networkProfile: { + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } + disableLocalAccounts: disableLocalAccounts && enableAad + addonProfiles: addOns + ingressProfile: { + webAppRouting: { + enabled: webAppRoutingAddon + } + } + } +} + +var aksDiagCategories = [ + 'cluster-autoscaler' + 'kube-controller-manager' + 'kube-audit-admin' + 'guard' +] + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'aks-diagnostics' + scope: aks + properties: { + workspaceId: workspaceId + logs: [for category in aksDiagCategories: { + category: category + enabled: true + }] + metrics: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +} + +@description('The resource name of the AKS cluster') +output clusterName string = aks.name + +@description('The AKS cluster identity') +output clusterIdentity object = { + clientId: aks.properties.identityProfile.kubeletidentity.clientId + objectId: aks.properties.identityProfile.kubeletidentity.objectId + resourceId: aks.properties.identityProfile.kubeletidentity.resourceId +} diff --git a/Environments/App-Base-WebApp-ACA/core/host/aks.bicep b/Environments/App-Base-WebApp-ACA/core/host/aks.bicep new file mode 100644 index 00000000..536a534b --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/aks.bicep @@ -0,0 +1,280 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool as well as an additional user agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name for the Azure container registry (ACR)') +param containerRegistryName string + +@description('The name of the connected log analytics workspace') +param logAnalyticsName string = '' + +@description('The name of the keyvault to grant access') +param keyVaultName string + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('AKS add-ons configuration') +param addOns object = { + azurePolicy: { + enabled: true + config: { + version: 'v2' + } + } + keyVault: { + enabled: true + config: { + enableSecretRotation: 'true' + rotationPollInterval: '2m' + } + } + openServiceMesh: { + enabled: false + config: {} + } + omsAgent: { + enabled: true + config: {} + } + applicationGateway: { + enabled: false + config: {} + } +} + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@allowed([ + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The System Pool Preset sizing') +param systemPoolType string = 'CostOptimised' + +@allowed([ + '' + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The User Pool Preset sizing') +param agentPoolType string = '' + +// Configure system / user agent pools +@description('Custom configuration of system node pool') +param systemPoolConfig object = {} +@description('Custom configuration of user node pool') +param agentPoolConfig object = {} + +@description('Id of the user or app to assign application roles') +param principalId string = '' + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// Configure AKS add-ons +var omsAgentConfig = (!empty(logAnalyticsName) && !empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? union( + addOns.omsAgent, + { + config: { + logAnalyticsWorkspaceResourceID: logAnalytics.id + } + } +) : {} + +var addOnsConfig = union( + (!empty(addOns.azurePolicy) && addOns.azurePolicy.enabled) ? { azurepolicy: addOns.azurePolicy } : {}, + (!empty(addOns.keyVault) && addOns.keyVault.enabled) ? { azureKeyvaultSecretsProvider: addOns.keyVault } : {}, + (!empty(addOns.openServiceMesh) && addOns.openServiceMesh.enabled) ? { openServiceMesh: addOns.openServiceMesh } : {}, + (!empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? { omsagent: omsAgentConfig } : {}, + (!empty(addOns.applicationGateway) && addOns.applicationGateway.enabled) ? { ingressApplicationGateway: addOns.applicationGateway } : {} +) + +// Link to existing log analytics workspace when available +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' existing = if (!empty(logAnalyticsName)) { + name: logAnalyticsName +} + +var systemPoolSpec = !empty(systemPoolConfig) ? systemPoolConfig : nodePoolPresets[systemPoolType] + +// Create the primary AKS cluster resources and system node pool +module managedCluster 'aks-managed-cluster.bicep' = { + name: 'managed-cluster' + params: { + name: name + location: location + tags: tags + systemPoolConfig: union( + { name: 'npsystem', mode: 'System' }, + nodePoolBase, + systemPoolSpec + ) + nodeResourceGroupName: nodeResourceGroupName + sku: sku + dnsPrefix: dnsPrefix + kubernetesVersion: kubernetesVersion + addOns: addOnsConfig + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + enableAad: enableAzureRbac && aadTenantId != '' + disableLocalAccounts: disableLocalAccounts + aadTenantId: aadTenantId + enableRbac: enableRbac + enableAzureRbac: enableAzureRbac + webAppRoutingAddon: webAppRoutingAddon + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } +} + +var hasAgentPool = !empty(agentPoolConfig) || !empty(agentPoolType) +var agentPoolSpec = hasAgentPool && !empty(agentPoolConfig) ? agentPoolConfig : empty(agentPoolType) ? {} : nodePoolPresets[agentPoolType] + +// Create additional user agent pool when specified +module agentPool 'aks-agent-pool.bicep' = if (hasAgentPool) { + name: 'aks-node-pool' + params: { + clusterName: managedCluster.outputs.clusterName + name: 'npuserpool' + config: union({ name: 'npuser', mode: 'User' }, nodePoolBase, agentPoolSpec) + } +} + +// Creates container registry (ACR) +module containerRegistry 'container-registry.bicep' = { + name: 'container-registry' + params: { + name: containerRegistryName + location: location + tags: tags + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + } +} + +// Grant ACR Pull access from cluster managed identity to container registry +module containerRegistryAccess '../security/registry-access.bicep' = { + name: 'cluster-container-registry-access' + params: { + containerRegistryName: containerRegistry.outputs.name + principalId: managedCluster.outputs.clusterIdentity.objectId + } +} + +// Give AKS cluster access to the specified principal +module clusterAccess '../security/aks-managed-cluster-access.bicep' = if (enableAzureRbac || disableLocalAccounts) { + name: 'cluster-access' + params: { + clusterName: managedCluster.outputs.clusterName + principalId: principalId + } +} + +// Give the AKS Cluster access to KeyVault +module clusterKeyVaultAccess '../security/keyvault-access.bicep' = { + name: 'cluster-keyvault-access' + params: { + keyVaultName: keyVaultName + principalId: managedCluster.outputs.clusterIdentity.objectId + } +} + +// Helpers for node pool configuration +var nodePoolBase = { + osType: 'Linux' + maxPods: 30 + type: 'VirtualMachineScaleSets' + upgradeSettings: { + maxSurge: '33%' + } +} + +var nodePoolPresets = { + CostOptimised: { + vmSize: 'Standard_B4ms' + count: 1 + minCount: 1 + maxCount: 3 + enableAutoScaling: true + availabilityZones: [] + } + Standard: { + vmSize: 'Standard_DS2_v2' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } + HighSpec: { + vmSize: 'Standard_D4s_v3' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } +} + +// Module outputs +@description('The resource name of the AKS cluster') +output clusterName string = managedCluster.outputs.clusterName + +@description('The AKS cluster identity') +output clusterIdentity object = managedCluster.outputs.clusterIdentity + +@description('The resource name of the ACR') +output containerRegistryName string = containerRegistry.outputs.name + +@description('The login server for the container registry') +output containerRegistryLoginServer string = containerRegistry.outputs.loginServer diff --git a/Environments/App-Base-WebApp-ACA/core/host/appservice-appsettings.bicep b/Environments/App-Base-WebApp-ACA/core/host/appservice-appsettings.bicep new file mode 100644 index 00000000..f4b22f81 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/appservice-appsettings.bicep @@ -0,0 +1,17 @@ +metadata description = 'Updates app settings for an Azure App Service.' +@description('The name of the app service resource within the current resource group scope') +param name string + +@description('The app settings to be applied to the app service') +@secure() +param appSettings object + +resource appService 'Microsoft.Web/sites@2022-03-01' existing = { + name: name +} + +resource settings 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'appsettings' + parent: appService + properties: appSettings +} diff --git a/Environments/App-Base-WebApp-ACA/core/host/appservice.bicep b/Environments/App-Base-WebApp-ACA/core/host/appservice.bicep new file mode 100644 index 00000000..bef4d2ba --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/appservice.bicep @@ -0,0 +1,123 @@ +metadata description = 'Creates an Azure App Service in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Microsoft.Web/sites Properties +param kind string = 'app,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = false +param use32BitWorkerProcess bool = false +param ftpsState string = 'FtpsOnly' +param healthCheckPath string = '' + +resource appService 'Microsoft.Web/sites@2022-03-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + serverFarmId: appServicePlanId + siteConfig: { + linuxFxVersion: linuxFxVersion + alwaysOn: alwaysOn + ftpsState: ftpsState + minTlsVersion: '1.2' + appCommandLine: appCommandLine + numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null + minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null + use32BitWorkerProcess: use32BitWorkerProcess + functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null + healthCheckPath: healthCheckPath + cors: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: true + } + + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + + resource basicPublishingCredentialsPoliciesFtp 'basicPublishingCredentialsPolicies' = { + name: 'ftp' + properties: { + allow: false + } + } + + resource basicPublishingCredentialsPoliciesScm 'basicPublishingCredentialsPolicies' = { + name: 'scm' + properties: { + allow: false + } + } +} + +// Updates to the single Microsoft.sites/web/config resources that need to be performed sequentially +// sites/web/config 'appsettings' +module configAppSettings 'appservice-appsettings.bicep' = { + name: '${name}-appSettings' + params: { + name: appService.name + appSettings: union(appSettings, + { + SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) + ENABLE_ORYX_BUILD: string(enableOryxBuild) + }, + runtimeName == 'python' && appCommandLine == '' ? { PYTHON_ENABLE_GUNICORN_MULTIWORKERS: 'true'} : {}, + !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, + !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) + } +} + +// sites/web/config 'logs' +resource configLogs 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'logs' + parent: appService + properties: { + applicationLogs: { fileSystem: { level: 'Verbose' } } + detailedErrorMessages: { enabled: true } + failedRequestsTracing: { enabled: true } + httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } + } + dependsOn: [configAppSettings] +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) { + name: keyVaultName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output identityPrincipalId string = managedIdentity ? appService.identity.principalId : '' +output name string = appService.name +output uri string = 'https://${appService.properties.defaultHostName}' diff --git a/Environments/App-Base-WebApp-ACA/core/host/appserviceplan.bicep b/Environments/App-Base-WebApp-ACA/core/host/appserviceplan.bicep new file mode 100644 index 00000000..2e37e041 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/appserviceplan.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param kind string = '' +param reserved bool = true +param sku object + +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + kind: kind + properties: { + reserved: reserved + } +} + +output id string = appServicePlan.id +output name string = appServicePlan.name diff --git a/Environments/App-Base-WebApp-ACA/core/host/container-app-upsert.bicep b/Environments/App-Base-WebApp-ACA/core/host/container-app-upsert.bicep new file mode 100644 index 00000000..3eec62f2 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/container-app-upsert.bicep @@ -0,0 +1,105 @@ +metadata description = 'Creates or updates an existing Azure Container App.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The environment name for the container apps') +param containerAppsEnvironmentName string + +@description('The number of CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('The amount of memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@allowed([ 'http', 'grpc' ]) +@description('The protocol used by Dapr to connect to the app, e.g., HTTP or gRPC') +param daprAppProtocol string = 'http' + +@description('Enable or disable Dapr for the container app') +param daprEnabled bool = false + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Specifies if the resource already exists') +param exists bool = false + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The name of the container image') +param imageName string = '' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The target port for the container') +param targetPort int = 80 + +resource existingApp 'Microsoft.App/containerApps@2023-04-01-preview' existing = if (exists) { + name: name +} + +module app 'container-app.bicep' = { + name: '${deployment().name}-update' + params: { + name: name + location: location + tags: tags + identityType: identityType + identityName: identityName + ingressEnabled: ingressEnabled + containerName: containerName + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: containerCpuCoreCount + containerMemory: containerMemory + containerMinReplicas: containerMinReplicas + containerMaxReplicas: containerMaxReplicas + daprEnabled: daprEnabled + daprAppId: daprAppId + daprAppProtocol: daprAppProtocol + secrets: secrets + external: external + env: env + imageName: !empty(imageName) ? imageName : exists ? existingApp.properties.template.containers[0].image : '' + targetPort: targetPort + serviceBinds: serviceBinds + } +} + +output defaultDomain string = app.outputs.defaultDomain +output imageName string = app.outputs.imageName +output name string = app.outputs.name +output uri string = app.outputs.uri diff --git a/Environments/App-Base-WebApp-ACA/core/host/container-app.bicep b/Environments/App-Base-WebApp-ACA/core/host/container-app.bicep new file mode 100644 index 00000000..3724086d --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/container-app.bicep @@ -0,0 +1,162 @@ +metadata description = 'Creates a container app in an Azure Container App environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Allowed origins') +param allowedOrigins array = [] + +@description('Name of the environment for container apps') +param containerAppsEnvironmentName string + +@description('CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('Memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@description('The protocol used by Dapr to connect to the app, e.g., http or grpc') +@allowed([ 'http', 'grpc' ]) +param daprAppProtocol string = 'http' + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Enable Dapr') +param daprEnabled bool = false + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the container image') +param imageName string = '' + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +param revisionMode string = 'Single' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The name of the container apps add-on to use. e.g. redis') +param serviceType string = '' + +@description('The target port for the container') +param targetPort int = 80 + +resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(identityName)) { + name: identityName +} + +// Private registry support requires both an ACR name and a User Assigned managed identity +var usePrivateRegistry = !empty(identityName) && !empty(containerRegistryName) + +// Automatically set to `UserAssigned` when an `identityName` has been set +var normalizedIdentityType = !empty(identityName) ? 'UserAssigned' : identityType + +module containerRegistryAccess '../security/registry-access.bicep' = if (usePrivateRegistry) { + name: '${deployment().name}-registry-access' + params: { + containerRegistryName: containerRegistryName + principalId: usePrivateRegistry ? userIdentity.properties.principalId : '' + } +} + +resource app 'Microsoft.App/containerApps@2023-04-01-preview' = { + name: name + location: location + tags: tags + // It is critical that the identity is granted ACR pull access before the app is created + // otherwise the container app will throw a provision error + // This also forces us to use an user assigned managed identity since there would no way to + // provide the system assigned identity with the ACR pull access before the app is created + dependsOn: usePrivateRegistry ? [ containerRegistryAccess ] : [] + identity: { + type: normalizedIdentityType + userAssignedIdentities: !empty(identityName) && normalizedIdentityType == 'UserAssigned' ? { '${userIdentity.id}': {} } : null + } + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + activeRevisionsMode: revisionMode + ingress: ingressEnabled ? { + external: external + targetPort: targetPort + transport: 'auto' + corsPolicy: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } : null + dapr: daprEnabled ? { + enabled: true + appId: daprAppId + appProtocol: daprAppProtocol + appPort: ingressEnabled ? targetPort : 0 + } : { enabled: false } + secrets: secrets + service: !empty(serviceType) ? { type: serviceType } : null + registries: usePrivateRegistry ? [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userIdentity.id + } + ] : [] + } + template: { + serviceBinds: !empty(serviceBinds) ? serviceBinds : null + containers: [ + { + image: !empty(imageName) ? imageName : 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: containerName + env: env + resources: { + cpu: json(containerCpuCoreCount) + memory: containerMemory + } + } + ] + scale: { + minReplicas: containerMinReplicas + maxReplicas: containerMaxReplicas + } + } + } +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' existing = { + name: containerAppsEnvironmentName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output identityPrincipalId string = normalizedIdentityType == 'None' ? '' : (empty(identityName) ? app.identity.principalId : userIdentity.properties.principalId) +output imageName string = imageName +output name string = app.name +output serviceBind object = !empty(serviceType) ? { serviceId: app.id, name: name } : {} +output uri string = ingressEnabled ? 'https://${app.properties.configuration.ingress.fqdn}' : '' diff --git a/Environments/App-Base-WebApp-ACA/core/host/container-apps-environment.bicep b/Environments/App-Base-WebApp-ACA/core/host/container-apps-environment.bicep new file mode 100644 index 00000000..8633ba48 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/container-apps-environment.bicep @@ -0,0 +1,41 @@ +metadata description = 'Creates an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the Application Insights resource') +param applicationInsightsName string = '' + +@description('Specifies if Dapr is enabled') +param daprEnabled bool = false + +@description('Name of the Log Analytics workspace') +param logAnalyticsWorkspaceName string + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: logAnalyticsWorkspaceName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output id string = containerAppsEnvironment.id +output name string = containerAppsEnvironment.name diff --git a/Environments/App-Base-WebApp-ACA/core/host/container-apps.bicep b/Environments/App-Base-WebApp-ACA/core/host/container-apps.bicep new file mode 100644 index 00000000..1c656e28 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/container-apps.bicep @@ -0,0 +1,40 @@ +metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param containerRegistryName string +param containerRegistryResourceGroupName string = '' +param containerRegistryAdminUserEnabled bool = false +param logAnalyticsWorkspaceName string +param applicationInsightsName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${name}-container-apps-environment' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + logAnalyticsWorkspaceName: logAnalyticsWorkspaceName + applicationInsightsName: applicationInsightsName + } +} + +module containerRegistry 'container-registry.bicep' = { + name: '${name}-container-registry' + scope: !empty(containerRegistryResourceGroupName) ? resourceGroup(containerRegistryResourceGroupName) : resourceGroup() + params: { + name: containerRegistryName + location: location + adminUserEnabled: containerRegistryAdminUserEnabled + tags: tags + } +} + +output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output environmentName string = containerAppsEnvironment.outputs.name +output environmentId string = containerAppsEnvironment.outputs.id + +output registryLoginServer string = containerRegistry.outputs.loginServer +output registryName string = containerRegistry.outputs.name diff --git a/Environments/App-Base-WebApp-ACA/core/host/container-registry.bicep b/Environments/App-Base-WebApp-ACA/core/host/container-registry.bicep new file mode 100644 index 00000000..9c64531b --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/container-registry.bicep @@ -0,0 +1,83 @@ +metadata description = 'Creates an Azure Container Registry.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Indicates whether admin user is enabled') +param adminUserEnabled bool = false + +@description('Indicates whether anonymous pull is enabled') +param anonymousPullEnabled bool = false + +@description('Indicates whether data endpoint is enabled') +param dataEndpointEnabled bool = false + +@description('Encryption settings') +param encryption object = { + status: 'disabled' +} + +@description('Options for bypassing network rules') +param networkRuleBypassOptions string = 'AzureServices' + +@description('Public network access setting') +param publicNetworkAccess string = 'Enabled' + +@description('SKU settings') +param sku object = { + name: 'Basic' +} + +@description('Zone redundancy setting') +param zoneRedundancy string = 'Disabled' + +@description('The log analytics workspace ID used for logging and monitoring') +param workspaceId string = '' + +// 2022-02-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { + name: name + location: location + tags: tags + sku: sku + properties: { + adminUserEnabled: adminUserEnabled + anonymousPullEnabled: anonymousPullEnabled + dataEndpointEnabled: dataEndpointEnabled + encryption: encryption + networkRuleBypassOptions: networkRuleBypassOptions + publicNetworkAccess: publicNetworkAccess + zoneRedundancy: zoneRedundancy + } +} + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'registry-diagnostics' + scope: containerRegistry + properties: { + workspaceId: workspaceId + logs: [ + { + category: 'ContainerRegistryRepositoryEvents' + enabled: true + } + { + category: 'ContainerRegistryLoginEvents' + enabled: true + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + timeGrain: 'PT1M' + } + ] + } +} + +output loginServer string = containerRegistry.properties.loginServer +output name string = containerRegistry.name diff --git a/Environments/App-Base-WebApp-ACA/core/host/functions.bicep b/Environments/App-Base-WebApp-ACA/core/host/functions.bicep new file mode 100644 index 00000000..7070a2c6 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/functions.bicep @@ -0,0 +1,86 @@ +metadata description = 'Creates an Azure Function in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) +param storageAccountName string + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Function Settings +@allowed([ + '~4', '~3', '~2', '~1' +]) +param extensionVersion string = '~4' + +// Microsoft.Web/sites Properties +param kind string = 'functionapp,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = true +param use32BitWorkerProcess bool = false +param healthCheckPath string = '' + +module functions 'appservice.bicep' = { + name: '${name}-functions' + params: { + name: name + location: location + tags: tags + allowedOrigins: allowedOrigins + alwaysOn: alwaysOn + appCommandLine: appCommandLine + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: union(appSettings, { + AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${storage.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' + FUNCTIONS_EXTENSION_VERSION: extensionVersion + FUNCTIONS_WORKER_RUNTIME: runtimeName + }) + clientAffinityEnabled: clientAffinityEnabled + enableOryxBuild: enableOryxBuild + functionAppScaleLimit: functionAppScaleLimit + healthCheckPath: healthCheckPath + keyVaultName: keyVaultName + kind: kind + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + minimumElasticInstanceCount: minimumElasticInstanceCount + numberOfWorkers: numberOfWorkers + runtimeName: runtimeName + runtimeVersion: runtimeVersion + runtimeNameAndVersion: runtimeNameAndVersion + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + use32BitWorkerProcess: use32BitWorkerProcess + } +} + +resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { + name: storageAccountName +} + +output identityPrincipalId string = managedIdentity ? functions.outputs.identityPrincipalId : '' +output name string = functions.outputs.name +output uri string = functions.outputs.uri diff --git a/Environments/App-Base-WebApp-ACA/core/host/staticwebapp.bicep b/Environments/App-Base-WebApp-ACA/core/host/staticwebapp.bicep new file mode 100644 index 00000000..cedaf906 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/host/staticwebapp.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Static Web Apps instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'Free' + tier: 'Free' +} + +resource web 'Microsoft.Web/staticSites@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + properties: { + provider: 'Custom' + } +} + +output name string = web.name +output uri string = 'https://${web.properties.defaultHostname}' diff --git a/Environments/App-Base-WebApp-ACA/core/monitor/applicationinsights-dashboard.bicep b/Environments/App-Base-WebApp-ACA/core/monitor/applicationinsights-dashboard.bicep new file mode 100644 index 00000000..d082e668 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/monitor/applicationinsights-dashboard.bicep @@ -0,0 +1,1236 @@ +metadata description = 'Creates a dashboard for an Application Insights instance.' +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} diff --git a/Environments/App-Base-WebApp-ACA/core/monitor/applicationinsights.bicep b/Environments/App-Base-WebApp-ACA/core/monitor/applicationinsights.bicep new file mode 100644 index 00000000..4b4d01e3 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/monitor/applicationinsights.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' +param name string +param dashboardName string = '' +param location string = resourceGroup().location +param tags object = {} +param logAnalyticsWorkspaceId string + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { + name: 'application-insights-dashboard' + params: { + name: dashboardName + location: location + applicationInsightsName: applicationInsights.name + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/Environments/App-Base-WebApp-ACA/core/monitor/loganalytics.bicep b/Environments/App-Base-WebApp-ACA/core/monitor/loganalytics.bicep new file mode 100644 index 00000000..33f9dc29 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/monitor/loganalytics.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates a Log Analytics workspace.' +param name string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: name + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/Environments/App-Base-WebApp-ACA/core/monitor/monitoring.bicep b/Environments/App-Base-WebApp-ACA/core/monitor/monitoring.bicep new file mode 100644 index 00000000..6bb05b0b --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/monitor/monitoring.bicep @@ -0,0 +1,32 @@ +metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string = '' +param location string = resourceGroup().location +param tags object = {} + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + name: logAnalyticsName + location: location + tags: tags + } +} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/Environments/App-Base-WebApp-ACA/core/networking/cdn-endpoint.bicep b/Environments/App-Base-WebApp-ACA/core/networking/cdn-endpoint.bicep new file mode 100644 index 00000000..5e8ab695 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/networking/cdn-endpoint.bicep @@ -0,0 +1,52 @@ +metadata description = 'Adds an endpoint to an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The name of the CDN profile resource') +@minLength(1) +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('The origin URL for the endpoint') +@minLength(1) +param originUrl string + +resource endpoint 'Microsoft.Cdn/profiles/endpoints@2022-05-01-preview' = { + parent: cdnProfile + name: name + location: location + tags: tags + properties: { + originHostHeader: originUrl + isHttpAllowed: false + isHttpsAllowed: true + queryStringCachingBehavior: 'UseQueryString' + optimizationType: 'GeneralWebDelivery' + origins: [ + { + name: replace(originUrl, '.', '-') + properties: { + hostName: originUrl + originHostHeader: originUrl + priority: 1 + weight: 1000 + enabled: true + } + } + ] + deliveryPolicy: { + rules: deliveryPolicyRules + } + } +} + +resource cdnProfile 'Microsoft.Cdn/profiles@2022-05-01-preview' existing = { + name: cdnProfileName +} + +output id string = endpoint.id +output name string = endpoint.name +output uri string = 'https://${endpoint.properties.hostName}' diff --git a/Environments/App-Base-WebApp-ACA/core/networking/cdn-profile.bicep b/Environments/App-Base-WebApp-ACA/core/networking/cdn-profile.bicep new file mode 100644 index 00000000..27669ee2 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/networking/cdn-profile.bicep @@ -0,0 +1,34 @@ +metadata description = 'Creates an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The pricing tier of this CDN profile') +@allowed([ + 'Custom_Verizon' + 'Premium_AzureFrontDoor' + 'Premium_Verizon' + 'StandardPlus_955BandWidth_ChinaCdn' + 'StandardPlus_AvgBandWidth_ChinaCdn' + 'StandardPlus_ChinaCdn' + 'Standard_955BandWidth_ChinaCdn' + 'Standard_Akamai' + 'Standard_AvgBandWidth_ChinaCdn' + 'Standard_AzureFrontDoor' + 'Standard_ChinaCdn' + 'Standard_Microsoft' + 'Standard_Verizon' +]) +param sku string = 'Standard_Microsoft' + +resource profile 'Microsoft.Cdn/profiles@2022-05-01-preview' = { + name: name + location: location + tags: tags + sku: { + name: sku + } +} + +output id string = profile.id +output name string = profile.name diff --git a/Environments/App-Base-WebApp-ACA/core/networking/cdn.bicep b/Environments/App-Base-WebApp-ACA/core/networking/cdn.bicep new file mode 100644 index 00000000..de98a1f9 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/networking/cdn.bicep @@ -0,0 +1,42 @@ +metadata description = 'Creates an Azure CDN profile with a single endpoint.' +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the CDN endpoint resource') +param cdnEndpointName string + +@description('Name of the CDN profile resource') +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('Origin URL for the CDN endpoint') +param originUrl string + +module cdnProfile 'cdn-profile.bicep' = { + name: 'cdn-profile' + params: { + name: cdnProfileName + location: location + tags: tags + } +} + +module cdnEndpoint 'cdn-endpoint.bicep' = { + name: 'cdn-endpoint' + params: { + name: cdnEndpointName + location: location + tags: tags + cdnProfileName: cdnProfile.outputs.name + originUrl: originUrl + deliveryPolicyRules: deliveryPolicyRules + } +} + +output endpointName string = cdnEndpoint.outputs.name +output endpointId string = cdnEndpoint.outputs.id +output profileName string = cdnProfile.outputs.name +output profileId string = cdnProfile.outputs.id +output uri string = cdnEndpoint.outputs.uri diff --git a/Environments/App-Base-WebApp-ACA/core/search/search-services.bicep b/Environments/App-Base-WebApp-ACA/core/search/search-services.bicep new file mode 100644 index 00000000..d9c619a9 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/search/search-services.bicep @@ -0,0 +1,68 @@ +metadata description = 'Creates an Azure AI Search instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'standard' +} + +param authOptions object = {} +param disableLocalAuth bool = false +param disabledDataExfiltrationOptions array = [] +param encryptionWithCmk object = { + enforcement: 'Unspecified' +} +@allowed([ + 'default' + 'highDensity' +]) +param hostingMode string = 'default' +param networkRuleSet object = { + bypass: 'None' + ipRules: [] +} +param partitionCount int = 1 +@allowed([ + 'enabled' + 'disabled' +]) +param publicNetworkAccess string = 'enabled' +param replicaCount int = 1 +@allowed([ + 'disabled' + 'free' + 'standard' +]) +param semanticSearch string = 'disabled' + +var searchIdentityProvider = (sku.name == 'free') ? null : { + type: 'SystemAssigned' +} + +resource search 'Microsoft.Search/searchServices@2021-04-01-preview' = { + name: name + location: location + tags: tags + // The free tier does not support managed identity + identity: searchIdentityProvider + properties: { + authOptions: authOptions + disableLocalAuth: disableLocalAuth + disabledDataExfiltrationOptions: disabledDataExfiltrationOptions + encryptionWithCmk: encryptionWithCmk + hostingMode: hostingMode + networkRuleSet: networkRuleSet + partitionCount: partitionCount + publicNetworkAccess: publicNetworkAccess + replicaCount: replicaCount + semanticSearch: semanticSearch + } + sku: sku +} + +output id string = search.id +output endpoint string = 'https://${name}.search.windows.net/' +output name string = search.name +output principalId string = !empty(searchIdentityProvider) ? search.identity.principalId : '' + diff --git a/Environments/App-Base-WebApp-ACA/core/security/aks-managed-cluster-access.bicep b/Environments/App-Base-WebApp-ACA/core/security/aks-managed-cluster-access.bicep new file mode 100644 index 00000000..dec984e8 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/security/aks-managed-cluster-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns RBAC role to the specified AKS cluster and principal.' +param clusterName string +param principalId string + +var aksClusterAdminRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b') + +resource aksRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aksCluster // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, aksClusterAdminRole) + properties: { + roleDefinitionId: aksClusterAdminRole + principalType: 'User' + principalId: principalId + } +} + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} diff --git a/Environments/App-Base-WebApp-ACA/core/security/keyvault-access.bicep b/Environments/App-Base-WebApp-ACA/core/security/keyvault-access.bicep new file mode 100644 index 00000000..316775f2 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/security/keyvault-access.bicep @@ -0,0 +1,22 @@ +metadata description = 'Assigns an Azure Key Vault access policy.' +param name string = 'add' + +param keyVaultName string +param permissions object = { secrets: [ 'get', 'list' ] } +param principalId string + +resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = { + parent: keyVault + name: name + properties: { + accessPolicies: [ { + objectId: principalId + tenantId: subscription().tenantId + permissions: permissions + } ] + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/App-Base-WebApp-ACA/core/security/keyvault-secret.bicep b/Environments/App-Base-WebApp-ACA/core/security/keyvault-secret.bicep new file mode 100644 index 00000000..7441b296 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/security/keyvault-secret.bicep @@ -0,0 +1,31 @@ +metadata description = 'Creates or updates a secret in an Azure Key Vault.' +param name string +param tags object = {} +param keyVaultName string +param contentType string = 'string' +@description('The value of the secret. Provide only derived values like blob storage access, but do not hard code any secrets in your templates') +@secure() +param secretValue string + +param enabled bool = true +param exp int = 0 +param nbf int = 0 + +resource keyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + name: name + tags: tags + parent: keyVault + properties: { + attributes: { + enabled: enabled + exp: exp + nbf: nbf + } + contentType: contentType + value: secretValue + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/App-Base-WebApp-ACA/core/security/keyvault.bicep b/Environments/App-Base-WebApp-ACA/core/security/keyvault.bicep new file mode 100644 index 00000000..314a1db6 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/security/keyvault.bicep @@ -0,0 +1,26 @@ +metadata description = 'Creates an Azure Key Vault.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param principalId string = '' + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: name + location: location + tags: tags + properties: { + tenantId: subscription().tenantId + sku: { family: 'A', name: 'standard' } + accessPolicies: !empty(principalId) ? [ + { + objectId: principalId + permissions: { secrets: [ 'get', 'list' ] } + tenantId: subscription().tenantId + } + ] : [] + } +} + +output endpoint string = keyVault.properties.vaultUri +output name string = keyVault.name diff --git a/Environments/App-Base-WebApp-ACA/core/security/registry-access.bicep b/Environments/App-Base-WebApp-ACA/core/security/registry-access.bicep new file mode 100644 index 00000000..5335efab --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/security/registry-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns ACR Pull permissions to access an Azure Container Registry.' +param containerRegistryName string +param principalId string + +var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + +resource aksAcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: containerRegistry // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, acrPullRole) + properties: { + roleDefinitionId: acrPullRole + principalType: 'ServicePrincipal' + principalId: principalId + } +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { + name: containerRegistryName +} diff --git a/Environments/App-Base-WebApp-ACA/core/security/role.bicep b/Environments/App-Base-WebApp-ACA/core/security/role.bicep new file mode 100644 index 00000000..0b30cfd3 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/security/role.bicep @@ -0,0 +1,21 @@ +metadata description = 'Creates a role assignment for a service principal.' +param principalId string + +@allowed([ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' +]) +param principalType string = 'ServicePrincipal' +param roleDefinitionId string + +resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) + properties: { + principalId: principalId + principalType: principalType + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) + } +} diff --git a/Environments/App-Base-WebApp-ACA/core/storage/storage-account.bicep b/Environments/App-Base-WebApp-ACA/core/storage/storage-account.bicep new file mode 100644 index 00000000..4b6febbe --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/storage/storage-account.bicep @@ -0,0 +1,64 @@ +metadata description = 'Creates an Azure storage account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@allowed([ + 'Cool' + 'Hot' + 'Premium' ]) +param accessTier string = 'Hot' +param allowBlobPublicAccess bool = true +param allowCrossTenantReplication bool = true +param allowSharedKeyAccess bool = true +param containers array = [] +param defaultToOAuthAuthentication bool = false +param deleteRetentionPolicy object = {} +@allowed([ 'AzureDnsZone', 'Standard' ]) +param dnsEndpointType string = 'Standard' +param kind string = 'StorageV2' +param minimumTlsVersion string = 'TLS1_2' +param supportsHttpsTrafficOnly bool = true +param networkAcls object = { + bypass: 'AzureServices' + defaultAction: 'Allow' +} +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { name: 'Standard_LRS' } + +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { + name: name + location: location + tags: tags + kind: kind + sku: sku + properties: { + accessTier: accessTier + allowBlobPublicAccess: allowBlobPublicAccess + allowCrossTenantReplication: allowCrossTenantReplication + allowSharedKeyAccess: allowSharedKeyAccess + defaultToOAuthAuthentication: defaultToOAuthAuthentication + dnsEndpointType: dnsEndpointType + minimumTlsVersion: minimumTlsVersion + networkAcls: networkAcls + publicNetworkAccess: publicNetworkAccess + supportsHttpsTrafficOnly: supportsHttpsTrafficOnly + } + + resource blobServices 'blobServices' = if (!empty(containers)) { + name: 'default' + properties: { + deleteRetentionPolicy: deleteRetentionPolicy + } + resource container 'containers' = [for container in containers: { + name: container.name + properties: { + publicAccess: contains(container, 'publicAccess') ? container.publicAccess : 'None' + } + }] + } +} + +output name string = storage.name +output primaryEndpoints object = storage.properties.primaryEndpoints diff --git a/Environments/App-Base-WebApp-ACA/core/testing/loadtesting.bicep b/Environments/App-Base-WebApp-ACA/core/testing/loadtesting.bicep new file mode 100644 index 00000000..46781086 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/core/testing/loadtesting.bicep @@ -0,0 +1,15 @@ +param name string +param location string = resourceGroup().location +param managedIdentity bool = false +param tags object = {} + +resource loadTest 'Microsoft.LoadTestService/loadTests@2022-12-01' = { + name: name + location: location + tags: tags + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + properties: { + } +} + +output loadTestingName string = loadTest.name diff --git a/Environments/App-Base-WebApp-ACA/main.bicep b/Environments/App-Base-WebApp-ACA/main.bicep new file mode 100644 index 00000000..25088009 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/main.bicep @@ -0,0 +1,178 @@ +@minLength(1) +@maxLength(64) +@description('Name of the the environment which is used to generate a short unique hash used in all resources.') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string = resourceGroup().location + +// Optional parameters to override the default azd resource naming conventions. Update the main.parameters.json file to provide values. e.g.,: +// "resourceGroupName": { +// "value": "myGroupName" +// } +param apiContainerAppName string = '' +param applicationInsightsDashboardName string = '' +param applicationInsightsName string = '' +param containerAppsEnvironmentName string = '' +param containerRegistryName string = '' +param cosmosAccountName string = '' +param cosmosDatabaseName string = '' +param keyVaultName string = '' +param logAnalyticsName string = '' +param webContainerAppName string = '' +param apimServiceName string = '' +param apiAppExists bool = false +param webAppExists bool = false + +@description('Flag to use Azure API Management to mediate the calls between the Web frontend and the backend API') +param useAPIM bool = false + +@description('Id of the user or app to assign application roles') +param principalId string = '' + +@description('The base URL used by the web service for sending API requests') +param webApiBaseUrl string = '' + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } +var apiContainerAppNameOrDefault = '${abbrs.appContainerApps}web-${resourceToken}' +var corsAcaUrl = 'https://${apiContainerAppNameOrDefault}.${containerApps.outputs.defaultDomain}' + +// Container apps host (including container registry) +module containerApps './core/host/container-apps.bicep' = { + name: 'container-apps' + params: { + name: 'app' + location: location + tags: tags + containerAppsEnvironmentName: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' + containerRegistryName: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + // Work around Azure/azure-dev#3157 (the root cause of which is Azure/acr#723) by explicitly enabling the admin user to allow users which + // don't have the `Owner` role granted (and instead are classic administrators) to access the registry to push even if AAD authentication fails. + // + // This addresses the following error during deploy: + // + // failed getting ACR token: POST https://.azurecr.io/oauth2/exchange 401 Unauthorized + containerRegistryAdminUserEnabled: true + logAnalyticsWorkspaceName: monitoring.outputs.logAnalyticsWorkspaceName + applicationInsightsName: monitoring.outputs.applicationInsightsName + } +} + +// Web frontend +module web './app/web.bicep' = { + name: 'web' + params: { + name: !empty(webContainerAppName) ? webContainerAppName : '${abbrs.appContainerApps}web-${resourceToken}' + location: location + tags: tags + identityName: '${abbrs.managedIdentityUserAssignedIdentities}web-${resourceToken}' + apiBaseUrl: !empty(webApiBaseUrl) ? webApiBaseUrl : api.outputs.SERVICE_API_URI + applicationInsightsName: monitoring.outputs.applicationInsightsName + containerAppsEnvironmentName: containerApps.outputs.environmentName + containerRegistryName: containerApps.outputs.registryName + exists: webAppExists + } +} + +// Api backend +module api './app/api.bicep' = { + name: 'api' + params: { + name: !empty(apiContainerAppName) ? apiContainerAppName : '${abbrs.appContainerApps}api-${resourceToken}' + location: location + tags: tags + identityName: '${abbrs.managedIdentityUserAssignedIdentities}api-${resourceToken}' + applicationInsightsName: monitoring.outputs.applicationInsightsName + containerAppsEnvironmentName: containerApps.outputs.environmentName + containerRegistryName: containerApps.outputs.registryName + keyVaultName: keyVault.outputs.name + corsAcaUrl: corsAcaUrl + exists: apiAppExists + } +} + +// The application database +module cosmos './app/db.bicep' = { + name: 'cosmos' + params: { + accountName: !empty(cosmosAccountName) ? cosmosAccountName : '${abbrs.documentDBDatabaseAccounts}${resourceToken}' + databaseName: cosmosDatabaseName + location: location + tags: tags + keyVaultName: keyVault.outputs.name + } +} + +// Store secrets in a keyvault +module keyVault './core/security/keyvault.bicep' = { + name: 'keyvault' + params: { + name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' + location: location + tags: tags + principalId: principalId + } +} + +// Monitor application with Azure Monitor +module monitoring './core/monitor/monitoring.bicep' = { + name: 'monitoring' + params: { + location: location + tags: tags + logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}' + } +} + +// Creates Azure API Management (APIM) service to mediate the requests between the frontend and the backend API +module apim './core/gateway/apim.bicep' = if (useAPIM) { + name: 'apim-deployment' + params: { + name: !empty(apimServiceName) ? apimServiceName : '${abbrs.apiManagementService}${resourceToken}' + location: location + tags: tags + applicationInsightsName: monitoring.outputs.applicationInsightsName + } +} + +// Configures the API in the Azure API Management (APIM) service +module apimApi './app/apim-api.bicep' = if (useAPIM) { + name: 'apim-api-deployment' + params: { + name: useAPIM ? apim.outputs.apimServiceName : '' + apiName: 'todo-api' + apiDisplayName: 'Simple Todo API' + apiDescription: 'This is a simple Todo API' + apiPath: 'todo' + webFrontendUrl: web.outputs.SERVICE_WEB_URI + apiBackendUrl: api.outputs.SERVICE_API_URI + } +} + +// Data outputs +output AZURE_COSMOS_CONNECTION_STRING_KEY string = cosmos.outputs.connectionStringKey +output AZURE_COSMOS_DATABASE_NAME string = cosmos.outputs.databaseName + +// App outputs +output API_CORS_ACA_URL string = corsAcaUrl +output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString +output APPLICATIONINSIGHTS_NAME string = monitoring.outputs.applicationInsightsName +output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerApps.outputs.environmentName +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerApps.outputs.registryLoginServer +output AZURE_CONTAINER_REGISTRY_NAME string = containerApps.outputs.registryName +output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.endpoint +output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId +output REACT_APP_API_BASE_URL string = useAPIM ? apimApi.outputs.SERVICE_API_URI : api.outputs.SERVICE_API_URI +output REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString +output REACT_APP_WEB_BASE_URL string = web.outputs.SERVICE_WEB_URI +output SERVICE_API_NAME string = api.outputs.SERVICE_API_NAME +output SERVICE_WEB_NAME string = web.outputs.SERVICE_WEB_NAME +output USE_APIM bool = useAPIM +output SERVICE_API_ENDPOINTS array = useAPIM ? [ apimApi.outputs.SERVICE_API_URI, api.outputs.SERVICE_API_URI ]: [] diff --git a/Environments/App-Base-WebApp-ACA/main.parameters.json b/Environments/App-Base-WebApp-ACA/main.parameters.json new file mode 100644 index 00000000..8ddb71e6 --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/main.parameters.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "principalId": { + "value": "${AZURE_PRINCIPAL_ID}" + }, + "apiAppExists": { + "value": "${SERVICE_API_RESOURCE_EXISTS=false}" + }, + "webAppExists": { + "value": "${SERVICE_WEB_RESOURCE_EXISTS=false}" + }, + "webApiBaseUrl": { + "value": "${REACT_APP_API_BASE_URL}" + }, + "useAPIM": { + "value": "${USE_APIM=false}" + } + } +} \ No newline at end of file diff --git a/Environments/App-Base-WebApp-ACA/manifest.yaml b/Environments/App-Base-WebApp-ACA/manifest.yaml new file mode 100644 index 00000000..277374ae --- /dev/null +++ b/Environments/App-Base-WebApp-ACA/manifest.yaml @@ -0,0 +1,19 @@ +name: App-Base-WebApp-ACA +version: 1.0.0 +summary: App-Base-Nodejs Mongo ACA +description: Deploys a base infra for ACA app dev with Nodejs and Mongo +runner: ARM +templatePath: azuredeploy.json + +parameters: +- id: "environmentName" + name: "Environment Name (e.g. testenv)" + description: "Name of the Environment" + type: string + required: true + +- id: "location" + name: "Region (e.g. eastus)" + description: "Location of the resources" + type: string + required: true diff --git a/Environments/App-Base-WebApp-AKS/abbreviations.json b/Environments/App-Base-WebApp-AKS/abbreviations.json new file mode 100644 index 00000000..289a08aa --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/abbreviations.json @@ -0,0 +1,136 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} \ No newline at end of file diff --git a/Environments/App-Base-WebApp-AKS/app/db.bicep b/Environments/App-Base-WebApp-AKS/app/db.bicep new file mode 100644 index 00000000..938949c6 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/app/db.bicep @@ -0,0 +1,40 @@ +param accountName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [ + { + name: 'TodoList' + id: 'TodoList' + shardKey: 'Hash' + indexKey: '_id' + } + { + name: 'TodoItem' + id: 'TodoItem' + shardKey: 'Hash' + indexKey: '_id' + } +] +param databaseName string = '' +param keyVaultName string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module cosmos '../core/database/cosmos/mongo/cosmos-mongo-db.bicep' = { + name: 'cosmos-mongo' + params: { + accountName: accountName + databaseName: actualDatabaseName + location: location + collections: collections + keyVaultName: keyVaultName + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = cosmos.outputs.databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/App-Base-WebApp-AKS/azuredeploy.json b/Environments/App-Base-WebApp-AKS/azuredeploy.json new file mode 100644 index 00000000..9626594f --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/azuredeploy.json @@ -0,0 +1,3384 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "13440997233145271475" + } + }, + "parameters": { + "environmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the the environment which is used to generate a short unique hash used in all resources." + } + }, + "location": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Primary location for all resources" + } + }, + "clusterName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource name of the AKS cluster" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource name of the Container Registry (ACR)" + } + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + }, + "cosmosAccountName": { + "type": "string", + "defaultValue": "" + }, + "cosmosDatabaseName": { + "type": "string", + "defaultValue": "" + }, + "keyVaultName": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "" + }, + "principalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Id of the user or app to assign application roles" + } + } + }, + "variables": { + "$fxv#0": { + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" + }, + "abbrs": "[variables('$fxv#0')]", + "resourceToken": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "tags": { + "azd-env-name": "[parameters('environmentName')]" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aks", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "name": "[if(not(empty(parameters('clusterName'))), createObject('value', parameters('clusterName')), createObject('value', format('{0}{1}', variables('abbrs').containerServiceManagedClusters, variables('resourceToken'))))]", + "containerRegistryName": "[if(not(empty(parameters('containerRegistryName'))), createObject('value', parameters('containerRegistryName')), createObject('value', format('{0}{1}', variables('abbrs').containerRegistryRegistries, variables('resourceToken'))))]", + "logAnalyticsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.logAnalyticsWorkspaceName.value]" + }, + "keyVaultName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyvault'), '2022-09-01').outputs.name.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "4109929557657560885" + }, + "description": "Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool as well as an additional user agent pool." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name for the AKS managed cluster" + } + }, + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The name for the Azure container registry (ACR)" + } + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the connected log analytics workspace" + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "The name of the keyvault to grant access" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The Azure region/location for the AKS resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom tags to apply to the AKS resources" + } + }, + "addOns": { + "type": "object", + "defaultValue": { + "azurePolicy": { + "enabled": true, + "config": { + "version": "v2" + } + }, + "keyVault": { + "enabled": true, + "config": { + "enableSecretRotation": "true", + "rotationPollInterval": "2m" + } + }, + "openServiceMesh": { + "enabled": false, + "config": {} + }, + "omsAgent": { + "enabled": true, + "config": {} + }, + "applicationGateway": { + "enabled": false, + "config": {} + } + }, + "metadata": { + "description": "AKS add-ons configuration" + } + }, + "sku": { + "type": "string", + "defaultValue": "Free", + "allowedValues": [ + "Free", + "Paid", + "Standard" + ], + "metadata": { + "description": "The managed cluster SKU." + } + }, + "loadBalancerSku": { + "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "basic", + "standard" + ], + "metadata": { + "description": "The load balancer SKU to use for ingress into the AKS cluster" + } + }, + "networkPlugin": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "kubenet", + "none" + ], + "metadata": { + "description": "Network plugin used for building the Kubernetes network." + } + }, + "networkPolicy": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "calico" + ], + "metadata": { + "description": "Network policy used for building the Kubernetes network." + } + }, + "dnsPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The DNS prefix to associate with the AKS cluster" + } + }, + "nodeResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the resource group for the managed resources of the AKS cluster" + } + }, + "systemPoolType": { + "type": "string", + "defaultValue": "CostOptimised", + "allowedValues": [ + "CostOptimised", + "Standard", + "HighSpec", + "Custom" + ], + "metadata": { + "description": "The System Pool Preset sizing" + } + }, + "agentPoolType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "CostOptimised", + "Standard", + "HighSpec", + "Custom" + ], + "metadata": { + "description": "The User Pool Preset sizing" + } + }, + "systemPoolConfig": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom configuration of system node pool" + } + }, + "agentPoolConfig": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom configuration of user node pool" + } + }, + "principalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Id of the user or app to assign application roles" + } + }, + "kubernetesVersion": { + "type": "string", + "defaultValue": "1.27.7", + "metadata": { + "description": "Kubernetes Version" + } + }, + "aadTenantId": { + "type": "string", + "defaultValue": "[tenant().tenantId]", + "metadata": { + "description": "The Tenant ID associated to the Azure Active Directory" + } + }, + "enableRbac": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether RBAC is enabled for local accounts" + } + }, + "disableLocalAccounts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "If set to true, getting static credentials will be disabled for this cluster." + } + }, + "enableAzureRbac": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable RBAC using AAD" + } + }, + "webAppRoutingAddon": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether web app routing (preview) add-on is enabled" + } + } + }, + "variables": { + "omsAgentConfig": "[if(and(and(not(empty(parameters('logAnalyticsName'))), not(empty(parameters('addOns').omsAgent))), parameters('addOns').omsAgent.enabled), union(parameters('addOns').omsAgent, createObject('config', createObject('logAnalyticsWorkspaceResourceID', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))))), createObject())]", + "addOnsConfig": "[union(if(and(not(empty(parameters('addOns').azurePolicy)), parameters('addOns').azurePolicy.enabled), createObject('azurepolicy', parameters('addOns').azurePolicy), createObject()), if(and(not(empty(parameters('addOns').keyVault)), parameters('addOns').keyVault.enabled), createObject('azureKeyvaultSecretsProvider', parameters('addOns').keyVault), createObject()), if(and(not(empty(parameters('addOns').openServiceMesh)), parameters('addOns').openServiceMesh.enabled), createObject('openServiceMesh', parameters('addOns').openServiceMesh), createObject()), if(and(not(empty(parameters('addOns').omsAgent)), parameters('addOns').omsAgent.enabled), createObject('omsagent', variables('omsAgentConfig')), createObject()), if(and(not(empty(parameters('addOns').applicationGateway)), parameters('addOns').applicationGateway.enabled), createObject('ingressApplicationGateway', parameters('addOns').applicationGateway), createObject()))]", + "systemPoolSpec": "[if(not(empty(parameters('systemPoolConfig'))), parameters('systemPoolConfig'), variables('nodePoolPresets')[parameters('systemPoolType')])]", + "hasAgentPool": "[or(not(empty(parameters('agentPoolConfig'))), not(empty(parameters('agentPoolType'))))]", + "agentPoolSpec": "[if(and(variables('hasAgentPool'), not(empty(parameters('agentPoolConfig')))), parameters('agentPoolConfig'), if(empty(parameters('agentPoolType')), createObject(), variables('nodePoolPresets')[parameters('agentPoolType')]))]", + "nodePoolBase": { + "osType": "Linux", + "maxPods": 30, + "type": "VirtualMachineScaleSets", + "upgradeSettings": { + "maxSurge": "33%" + } + }, + "nodePoolPresets": { + "CostOptimised": { + "vmSize": "Standard_B4ms", + "count": 1, + "minCount": 1, + "maxCount": 3, + "enableAutoScaling": true, + "availabilityZones": [] + }, + "Standard": { + "vmSize": "Standard_DS2_v2", + "count": 3, + "minCount": 3, + "maxCount": 5, + "enableAutoScaling": true, + "availabilityZones": [ + "1", + "2", + "3" + ] + }, + "HighSpec": { + "vmSize": "Standard_D4s_v3", + "count": 3, + "minCount": 3, + "maxCount": 5, + "enableAutoScaling": true, + "availabilityZones": [ + "1", + "2", + "3" + ] + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "managed-cluster", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "systemPoolConfig": { + "value": "[union(createObject('name', 'npsystem', 'mode', 'System'), variables('nodePoolBase'), variables('systemPoolSpec'))]" + }, + "nodeResourceGroupName": { + "value": "[parameters('nodeResourceGroupName')]" + }, + "sku": { + "value": "[parameters('sku')]" + }, + "dnsPrefix": { + "value": "[parameters('dnsPrefix')]" + }, + "kubernetesVersion": { + "value": "[parameters('kubernetesVersion')]" + }, + "addOns": { + "value": "[variables('addOnsConfig')]" + }, + "workspaceId": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))), createObject('value', ''))]", + "enableAad": { + "value": "[and(parameters('enableAzureRbac'), not(equals(parameters('aadTenantId'), '')))]" + }, + "disableLocalAccounts": { + "value": "[parameters('disableLocalAccounts')]" + }, + "aadTenantId": { + "value": "[parameters('aadTenantId')]" + }, + "enableRbac": { + "value": "[parameters('enableRbac')]" + }, + "enableAzureRbac": { + "value": "[parameters('enableAzureRbac')]" + }, + "webAppRoutingAddon": { + "value": "[parameters('webAppRoutingAddon')]" + }, + "loadBalancerSku": { + "value": "[parameters('loadBalancerSku')]" + }, + "networkPlugin": { + "value": "[parameters('networkPlugin')]" + }, + "networkPolicy": { + "value": "[parameters('networkPolicy')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8184151159222198677" + }, + "description": "Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name for the AKS managed cluster" + } + }, + "nodeResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the resource group for the managed resources of the AKS cluster" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The Azure region/location for the AKS resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom tags to apply to the AKS resources" + } + }, + "kubernetesVersion": { + "type": "string", + "defaultValue": "1.27.7", + "metadata": { + "description": "Kubernetes Version" + } + }, + "enableRbac": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether RBAC is enabled for local accounts" + } + }, + "webAppRoutingAddon": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether web app routing (preview) add-on is enabled" + } + }, + "enableAad": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable Azure Active Directory integration" + } + }, + "enableAzureRbac": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable RBAC using AAD" + } + }, + "aadTenantId": { + "type": "string", + "defaultValue": "[tenant().tenantId]", + "metadata": { + "description": "The Tenant ID associated to the Azure Active Directory" + } + }, + "loadBalancerSku": { + "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "basic", + "standard" + ], + "metadata": { + "description": "The load balancer SKU to use for ingress into the AKS cluster" + } + }, + "networkPlugin": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "kubenet", + "none" + ], + "metadata": { + "description": "Network plugin used for building the Kubernetes network." + } + }, + "networkPolicy": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "calico" + ], + "metadata": { + "description": "Network policy used for building the Kubernetes network." + } + }, + "disableLocalAccounts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "If set to true, getting static credentials will be disabled for this cluster." + } + }, + "sku": { + "type": "string", + "defaultValue": "Free", + "allowedValues": [ + "Free", + "Paid", + "Standard" + ], + "metadata": { + "description": "The managed cluster SKU." + } + }, + "addOns": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Configuration of AKS add-ons" + } + }, + "workspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The log analytics workspace id used for logging & monitoring" + } + }, + "systemPoolConfig": { + "type": "object", + "metadata": { + "description": "The node pool configuration for the System agent pool" + } + }, + "dnsPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The DNS prefix to associate with the AKS cluster" + } + } + }, + "variables": { + "aksDiagCategories": [ + "cluster-autoscaler", + "kube-controller-manager", + "kube-audit-admin", + "guard" + ] + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters", + "apiVersion": "2023-10-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "sku": { + "name": "Base", + "tier": "[parameters('sku')]" + }, + "properties": { + "nodeResourceGroup": "[if(not(empty(parameters('nodeResourceGroupName'))), parameters('nodeResourceGroupName'), format('rg-mc-{0}', parameters('name')))]", + "kubernetesVersion": "[parameters('kubernetesVersion')]", + "dnsPrefix": "[if(empty(parameters('dnsPrefix')), format('{0}-dns', parameters('name')), parameters('dnsPrefix'))]", + "enableRBAC": "[parameters('enableRbac')]", + "aadProfile": "[if(parameters('enableAad'), createObject('managed', true(), 'enableAzureRBAC', parameters('enableAzureRbac'), 'tenantID', parameters('aadTenantId')), null())]", + "agentPoolProfiles": [ + "[parameters('systemPoolConfig')]" + ], + "networkProfile": { + "loadBalancerSku": "[parameters('loadBalancerSku')]", + "networkPlugin": "[parameters('networkPlugin')]", + "networkPolicy": "[parameters('networkPolicy')]" + }, + "disableLocalAccounts": "[and(parameters('disableLocalAccounts'), parameters('enableAad'))]", + "addonProfiles": "[parameters('addOns')]", + "ingressProfile": { + "webAppRouting": { + "enabled": "[parameters('webAppRoutingAddon')]" + } + } + } + }, + { + "condition": "[not(empty(parameters('workspaceId')))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerService/managedClusters/{0}', parameters('name'))]", + "name": "aks-diagnostics", + "properties": { + "copy": [ + { + "name": "logs", + "count": "[length(variables('aksDiagCategories'))]", + "input": { + "category": "[variables('aksDiagCategories')[copyIndex('logs')]]", + "enabled": true + } + } + ], + "workspaceId": "[parameters('workspaceId')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerService/managedClusters', parameters('name'))]" + ] + } + ], + "outputs": { + "clusterName": { + "type": "string", + "metadata": { + "description": "The resource name of the AKS cluster" + }, + "value": "[parameters('name')]" + }, + "clusterIdentity": { + "type": "object", + "metadata": { + "description": "The AKS cluster identity" + }, + "value": { + "clientId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.clientId]", + "objectId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.objectId]", + "resourceId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.resourceId]" + } + } + } + } + } + }, + { + "condition": "[variables('hasAgentPool')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aks-node-pool", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "name": { + "value": "npuserpool" + }, + "config": { + "value": "[union(createObject('name', 'npuser', 'mode', 'User'), variables('nodePoolBase'), variables('agentPoolSpec'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "6072270897511874144" + }, + "description": "Adds an agent pool to an Azure Kubernetes Service (AKS) cluster." + }, + "parameters": { + "clusterName": { + "type": "string" + }, + "name": { + "type": "string", + "metadata": { + "description": "The agent pool name" + } + }, + "config": { + "type": "object", + "metadata": { + "description": "The agent pool configuration" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters/agentPools", + "apiVersion": "2023-10-02-preview", + "name": "[format('{0}/{1}', parameters('clusterName'), parameters('name'))]", + "properties": "[parameters('config')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-registry", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerRegistryName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "workspaceId": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "12834334744516280883" + }, + "description": "Creates an Azure Container Registry." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "adminUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether admin user is enabled" + } + }, + "anonymousPullEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether anonymous pull is enabled" + } + }, + "dataEndpointEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether data endpoint is enabled" + } + }, + "encryption": { + "type": "object", + "defaultValue": { + "status": "disabled" + }, + "metadata": { + "description": "Encryption settings" + } + }, + "networkRuleBypassOptions": { + "type": "string", + "defaultValue": "AzureServices", + "metadata": { + "description": "Options for bypassing network rules" + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Public network access setting" + } + }, + "sku": { + "type": "object", + "defaultValue": { + "name": "Basic" + }, + "metadata": { + "description": "SKU settings" + } + }, + "zoneRedundancy": { + "type": "string", + "defaultValue": "Disabled", + "metadata": { + "description": "Zone redundancy setting" + } + }, + "workspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The log analytics workspace ID used for logging and monitoring" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2022-02-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": "[parameters('sku')]", + "properties": { + "adminUserEnabled": "[parameters('adminUserEnabled')]", + "anonymousPullEnabled": "[parameters('anonymousPullEnabled')]", + "dataEndpointEnabled": "[parameters('dataEndpointEnabled')]", + "encryption": "[parameters('encryption')]", + "networkRuleBypassOptions": "[parameters('networkRuleBypassOptions')]", + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "zoneRedundancy": "[parameters('zoneRedundancy')]" + } + }, + { + "condition": "[not(empty(parameters('workspaceId')))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "registry-diagnostics", + "properties": { + "workspaceId": "[parameters('workspaceId')]", + "logs": [ + { + "category": "ContainerRegistryRepositoryEvents", + "enabled": true + }, + { + "category": "ContainerRegistryLoginEvents", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true, + "timeGrain": "PT1M" + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" + ] + } + ], + "outputs": { + "loginServer": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), '2022-02-01-preview').loginServer]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cluster-container-registry-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerRegistryName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.name.value]" + }, + "principalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterIdentity.value.objectId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "15144906240959446537" + }, + "description": "Assigns ACR Pull permissions to access an Azure Container Registry." + }, + "parameters": { + "containerRegistryName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "acrPullRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('containerRegistryName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('acrPullRole'))]", + "properties": { + "roleDefinitionId": "[variables('acrPullRole')]", + "principalType": "ServicePrincipal", + "principalId": "[parameters('principalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'container-registry')]", + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + }, + { + "condition": "[or(parameters('enableAzureRbac'), parameters('disableLocalAccounts'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cluster-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "principalId": { + "value": "[parameters('principalId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8205013527918052324" + }, + "description": "Assigns RBAC role to the specified AKS cluster and principal." + }, + "parameters": { + "clusterName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "aksClusterAdminRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerService/managedClusters/{0}', parameters('clusterName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('aksClusterAdminRole'))]", + "properties": { + "roleDefinitionId": "[variables('aksClusterAdminRole')]", + "principalType": "User", + "principalId": "[parameters('principalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cluster-keyvault-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "principalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterIdentity.value.objectId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "815983560956742247" + }, + "description": "Assigns an Azure Key Vault access policy." + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "add" + }, + "keyVaultName": { + "type": "string" + }, + "permissions": { + "type": "object", + "defaultValue": { + "secrets": [ + "get", + "list" + ] + } + }, + "principalId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "properties": { + "accessPolicies": [ + { + "objectId": "[parameters('principalId')]", + "tenantId": "[subscription().tenantId]", + "permissions": "[parameters('permissions')]" + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + } + ], + "outputs": { + "clusterName": { + "type": "string", + "metadata": { + "description": "The resource name of the AKS cluster" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "clusterIdentity": { + "type": "object", + "metadata": { + "description": "The AKS cluster identity" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterIdentity.value]" + }, + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The resource name of the ACR" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.name.value]" + }, + "containerRegistryLoginServer": { + "type": "string", + "metadata": { + "description": "The login server for the container registry" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.loginServer.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'keyvault')]", + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cosmos", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "accountName": "[if(not(empty(parameters('cosmosAccountName'))), createObject('value', parameters('cosmosAccountName')), createObject('value', format('{0}{1}', variables('abbrs').documentDBDatabaseAccounts, variables('resourceToken'))))]", + "databaseName": { + "value": "[parameters('cosmosDatabaseName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "keyVaultName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyvault'), '2022-09-01').outputs.name.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "5730728686647632614" + } + }, + "parameters": { + "accountName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "collections": { + "type": "array", + "defaultValue": [ + { + "name": "TodoList", + "id": "TodoList", + "shardKey": "Hash", + "indexKey": "_id" + }, + { + "name": "TodoItem", + "id": "TodoItem", + "shardKey": "Hash", + "indexKey": "_id" + } + ] + }, + "databaseName": { + "type": "string", + "defaultValue": "" + }, + "keyVaultName": { + "type": "string" + } + }, + "variables": { + "defaultDatabaseName": "Todo", + "actualDatabaseName": "[if(not(empty(parameters('databaseName'))), parameters('databaseName'), variables('defaultDatabaseName'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cosmos-mongo", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "accountName": { + "value": "[parameters('accountName')]" + }, + "databaseName": { + "value": "[variables('actualDatabaseName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "collections": { + "value": "[parameters('collections')]" + }, + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "14549161001187918251" + }, + "description": "Creates an Azure Cosmos DB for MongoDB account with a database." + }, + "parameters": { + "accountName": { + "type": "string" + }, + "databaseName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "collections": { + "type": "array", + "defaultValue": [] + }, + "connectionStringKey": { + "type": "string", + "defaultValue": "AZURE-COSMOS-CONNECTION-STRING" + }, + "keyVaultName": { + "type": "string" + } + }, + "resources": [ + { + "copy": { + "name": "list", + "count": "[length(parameters('collections'))]" + }, + "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections", + "apiVersion": "2022-08-15", + "name": "[format('{0}/{1}/{2}', split(format('{0}/{1}', parameters('accountName'), parameters('databaseName')), '/')[0], split(format('{0}/{1}', parameters('accountName'), parameters('databaseName')), '/')[1], parameters('collections')[copyIndex()].name)]", + "properties": { + "resource": { + "id": "[parameters('collections')[copyIndex()].id]", + "shardKey": { + "_id": "[parameters('collections')[copyIndex()].shardKey]" + }, + "indexes": [ + { + "key": { + "keys": [ + "[parameters('collections')[copyIndex()].indexKey]" + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases', split(format('{0}/{1}', parameters('accountName'), parameters('databaseName')), '/')[0], split(format('{0}/{1}', parameters('accountName'), parameters('databaseName')), '/')[1])]" + ] + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases", + "apiVersion": "2022-08-15", + "name": "[format('{0}/{1}', parameters('accountName'), parameters('databaseName'))]", + "tags": "[parameters('tags')]", + "properties": { + "resource": { + "id": "[parameters('databaseName')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'cosmos-mongo-account')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cosmos-mongo-account", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('accountName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "connectionStringKey": { + "value": "[parameters('connectionStringKey')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8317058180807592714" + }, + "description": "Creates an Azure Cosmos DB for MongoDB account." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "keyVaultName": { + "type": "string" + }, + "connectionStringKey": { + "type": "string", + "defaultValue": "AZURE-COSMOS-CONNECTION-STRING" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cosmos-account", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "connectionStringKey": { + "value": "[parameters('connectionStringKey')]" + }, + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "kind": { + "value": "MongoDB" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "13614361263700788271" + }, + "description": "Creates an Azure Cosmos DB account." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "connectionStringKey": { + "type": "string", + "defaultValue": "AZURE-COSMOS-CONNECTION-STRING" + }, + "keyVaultName": { + "type": "string" + }, + "kind": { + "type": "string", + "allowedValues": [ + "GlobalDocumentDB", + "MongoDB", + "Parse" + ] + } + }, + "resources": [ + { + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2022-08-15", + "name": "[parameters('name')]", + "kind": "[parameters('kind')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "consistencyPolicy": { + "defaultConsistencyLevel": "Session" + }, + "locations": [ + { + "locationName": "[parameters('location')]", + "failoverPriority": 0, + "isZoneRedundant": false + } + ], + "databaseAccountOfferType": "Standard", + "enableAutomaticFailover": false, + "enableMultipleWriteLocations": false, + "apiProperties": "[if(equals(parameters('kind'), 'MongoDB'), createObject('serverVersion', '4.2'), createObject())]", + "capabilities": [ + { + "name": "EnableServerless" + } + ] + } + }, + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('connectionStringKey'))]", + "properties": { + "value": "[listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2022-08-15').connectionStrings[0].connectionString]" + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionStringKey": { + "type": "string", + "value": "[parameters('connectionStringKey')]" + }, + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2022-08-15').documentEndpoint]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + } + ], + "outputs": { + "connectionStringKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-account'), '2022-09-01').outputs.connectionStringKey.value]" + }, + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-account'), '2022-09-01').outputs.endpoint.value]" + }, + "id": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-account'), '2022-09-01').outputs.id.value]" + } + } + } + } + } + ], + "outputs": { + "connectionStringKey": { + "type": "string", + "value": "[parameters('connectionStringKey')]" + }, + "databaseName": { + "type": "string", + "value": "[parameters('databaseName')]" + }, + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-mongo-account'), '2022-09-01').outputs.endpoint.value]" + } + } + } + } + } + ], + "outputs": { + "connectionStringKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-mongo'), '2022-09-01').outputs.connectionStringKey.value]" + }, + "databaseName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-mongo'), '2022-09-01').outputs.databaseName.value]" + }, + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos-mongo'), '2022-09-01').outputs.endpoint.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'keyvault')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "keyvault", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(not(empty(parameters('keyVaultName'))), createObject('value', parameters('keyVaultName')), createObject('value', format('{0}{1}', variables('abbrs').keyVaultVaults, variables('resourceToken'))))]", + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "principalId": { + "value": "[parameters('principalId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "17948623451174129396" + }, + "description": "Creates an Azure Key Vault." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "principalId": { + "type": "string", + "defaultValue": "" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "tenantId": "[subscription().tenantId]", + "sku": { + "family": "A", + "name": "standard" + }, + "accessPolicies": "[if(not(empty(parameters('principalId'))), createArray(createObject('objectId', parameters('principalId'), 'permissions', createObject('secrets', createArray('get', 'list')), 'tenantId', subscription().tenantId)), createArray())]" + } + } + ], + "outputs": { + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '2022-07-01').vaultUri]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "monitoring", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "logAnalyticsName": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', parameters('logAnalyticsName')), createObject('value', format('{0}{1}', variables('abbrs').operationalInsightsWorkspaces, variables('resourceToken'))))]", + "applicationInsightsName": "[if(not(empty(parameters('applicationInsightsName'))), createObject('value', parameters('applicationInsightsName')), createObject('value', format('{0}{1}', variables('abbrs').insightsComponents, variables('resourceToken'))))]", + "applicationInsightsDashboardName": "[if(not(empty(parameters('applicationInsightsDashboardName'))), createObject('value', parameters('applicationInsightsDashboardName')), createObject('value', format('{0}{1}', variables('abbrs').portalDashboards, variables('resourceToken'))))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10041669792322197047" + }, + "description": "Creates an Application Insights instance and a Log Analytics workspace." + }, + "parameters": { + "logAnalyticsName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "loganalytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('logAnalyticsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "9622176141085970536" + }, + "description": "Creates a Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-12-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": 30, + "features": { + "searchVersion": 1 + }, + "sku": { + "name": "PerGB2018" + } + } + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "applicationinsights", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('applicationInsightsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "dashboardName": { + "value": "[parameters('applicationInsightsDashboardName')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1335628967363670282" + }, + "description": "Creates an Application Insights instance based on an existing Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "dashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "logAnalyticsWorkspaceId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]" + } + }, + { + "condition": "[not(empty(parameters('dashboardName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "application-insights-dashboard", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('dashboardName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "applicationInsightsName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "2145880658446193205" + }, + "description": "Creates a dashboard for an Application Insights instance." + }, + "parameters": { + "name": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Portal/dashboards", + "apiVersion": "2020-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "lenses": [ + { + "order": 0, + "parts": [ + { + "position": { + "x": 0, + "y": 0, + "colSpan": 2, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "id", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart", + "asset": { + "idInputName": "id", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "overview" + } + }, + { + "position": { + "x": 2, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "ProactiveDetection" + } + }, + { + "position": { + "x": 3, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + } + ], + "type": "Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:20:33.345Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 5, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-08T18:47:35.237Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "ConfigurationId", + "value": "78ce933e-e864-4b05-a27b-71fd55a6afad" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AppMapButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 0, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Usage", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 3, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:22:35.782Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + } + ], + "type": "Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Reliability", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 7, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:42:40.072Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "failures" + } + }, + { + "position": { + "x": 8, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Responsiveness\r\n", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 11, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:43:37.804Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "2a8ede4f-2bee-4b9c-aed9-2db0e8a01865", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "performance" + } + }, + { + "position": { + "x": 12, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Browser", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 15, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "MetricsExplorerJsonDefinitionId", + "value": "BrowserPerformanceTimelineMetrics" + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "createdTime": "2018-05-08T12:16:27.534Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "CurrentFilter", + "value": { + "eventTypes": [ + 4, + 1, + 3, + 5, + 2, + 6, + 13 + ], + "typeFacets": {}, + "isPermissive": false + } + }, + { + "name": "id", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "browser" + } + }, + { + "position": { + "x": 0, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "sessions/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Sessions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "users/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Users", + "color": "#7E58FF" + } + } + ], + "title": "Unique sessions and users", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "segmentationUsers" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Failed requests", + "color": "#EC008C" + } + } + ], + "title": "Failed requests", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "failures" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/duration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server response time", + "color": "#00BCF2" + } + } + ], + "title": "Server response time", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "performance" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/networkDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Page load network connect time", + "color": "#7E58FF" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/processingDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Client processing time", + "color": "#44F1C8" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/sendDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Send request time", + "color": "#EB9371" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/receiveDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Receiving response time", + "color": "#0672F1" + } + } + ], + "title": "Average page load time breakdown", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/availabilityPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability", + "color": "#47BDF5" + } + } + ], + "title": "Average availability", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "availability" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/server", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server exceptions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "dependencies/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Dependency failures", + "color": "#7E58FF" + } + } + ], + "title": "Server exceptions and Dependency failures", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processorCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Processor time", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process CPU", + "color": "#7E58FF" + } + } + ], + "title": "Average processor and process CPU utilization", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/browser", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Browser exceptions", + "color": "#47BDF5" + } + } + ], + "title": "Browser exceptions", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/count", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability test results count", + "color": "#47BDF5" + } + } + ], + "title": "Availability test results count", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processIOBytesPerSecond", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process IO rate", + "color": "#47BDF5" + } + } + ], + "title": "Average process I/O rate", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/memoryAvailableBytes", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Available memory", + "color": "#47BDF5" + } + } + ], + "title": "Average available memory", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + } + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').ConnectionString]" + }, + "instrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').InstrumentationKey]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'loganalytics')]" + ] + } + ], + "outputs": { + "applicationInsightsConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.connectionString.value]" + }, + "applicationInsightsInstrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "applicationInsightsName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.name.value]" + } + } + } + } + } + ], + "outputs": { + "AZURE_COSMOS_CONNECTION_STRING_KEY": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos'), '2022-09-01').outputs.connectionStringKey.value]" + }, + "AZURE_COSMOS_DATABASE_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cosmos'), '2022-09-01').outputs.databaseName.value]" + }, + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "AZURE_KEY_VAULT_ENDPOINT": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyvault'), '2022-09-01').outputs.endpoint.value]" + }, + "AZURE_KEY_VAULT_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'keyvault'), '2022-09-01').outputs.name.value]" + }, + "AZURE_LOCATION": { + "type": "string", + "value": "[parameters('location')]" + }, + "AZURE_TENANT_ID": { + "type": "string", + "value": "[tenant().tenantId]" + }, + "AZURE_AKS_CLUSTER_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.clusterName.value]" + }, + "AZURE_AKS_IDENTITY_CLIENT_ID": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.clusterIdentity.value.clientId]" + }, + "AZURE_CONTAINER_REGISTRY_ENDPOINT": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.containerRegistryLoginServer.value]" + }, + "AZURE_CONTAINER_REGISTRY_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.containerRegistryName.value]" + }, + "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/Environments/App-Base-WebApp-AKS/core/ai/cognitiveservices.bicep b/Environments/App-Base-WebApp-AKS/core/ai/cognitiveservices.bicep new file mode 100644 index 00000000..1bf5666b --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/ai/cognitiveservices.bicep @@ -0,0 +1,53 @@ +metadata description = 'Creates an Azure Cognitive Services instance.' +param name string +param location string = resourceGroup().location +param tags object = {} +@description('The custom subdomain name used to access the API. Defaults to the value of the name parameter.') +param customSubDomainName string = name +param deployments array = [] +param kind string = 'OpenAI' + +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { + name: 'S0' +} + +param allowedIpRules array = [] +param networkAcls object = empty(allowedIpRules) ? { + defaultAction: 'Allow' +} : { + ipRules: allowedIpRules + defaultAction: 'Deny' +} + +resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + customSubDomainName: customSubDomainName + publicNetworkAccess: publicNetworkAccess + networkAcls: networkAcls + } + sku: sku +} + +@batchSize(1) +resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: { + parent: account + name: deployment.name + properties: { + model: deployment.model + raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null + } + sku: contains(deployment, 'sku') ? deployment.sku : { + name: 'Standard' + capacity: 20 + } +}] + +output endpoint string = account.properties.endpoint +output id string = account.id +output name string = account.name diff --git a/Environments/App-Base-WebApp-AKS/core/database/cosmos/cosmos-account.bicep b/Environments/App-Base-WebApp-AKS/core/database/cosmos/cosmos-account.bicep new file mode 100644 index 00000000..6f8747f5 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/database/cosmos/cosmos-account.bicep @@ -0,0 +1,49 @@ +metadata description = 'Creates an Azure Cosmos DB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +@allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) +param kind string + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { + name: name + kind: kind + location: location + tags: tags + properties: { + consistencyPolicy: { defaultConsistencyLevel: 'Session' } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + databaseAccountOfferType: 'Standard' + enableAutomaticFailover: false + enableMultipleWriteLocations: false + apiProperties: (kind == 'MongoDB') ? { serverVersion: '4.2' } : {} + capabilities: [ { name: 'EnableServerless' } ] + } +} + +resource cosmosConnectionString 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: cosmos.listConnectionStrings().connectionStrings[0].connectionString + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output connectionStringKey string = connectionStringKey +output endpoint string = cosmos.properties.documentEndpoint +output id string = cosmos.id +output name string = cosmos.name diff --git a/Environments/App-Base-WebApp-AKS/core/database/cosmos/mongo/cosmos-mongo-account.bicep b/Environments/App-Base-WebApp-AKS/core/database/cosmos/mongo/cosmos-mongo-account.bicep new file mode 100644 index 00000000..4aafbf38 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/database/cosmos/mongo/cosmos-mongo-account.bicep @@ -0,0 +1,23 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + connectionStringKey: connectionStringKey + keyVaultName: keyVaultName + kind: 'MongoDB' + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id diff --git a/Environments/App-Base-WebApp-AKS/core/database/cosmos/mongo/cosmos-mongo-db.bicep b/Environments/App-Base-WebApp-AKS/core/database/cosmos/mongo/cosmos-mongo-db.bicep new file mode 100644 index 00000000..2a670578 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/database/cosmos/mongo/cosmos-mongo-db.bicep @@ -0,0 +1,47 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [] +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +module cosmos 'cosmos-mongo-account.bicep' = { + name: 'cosmos-mongo-account' + params: { + name: accountName + location: location + keyVaultName: keyVaultName + tags: tags + connectionStringKey: connectionStringKey + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2022-08-15' = { + name: '${accountName}/${databaseName}' + tags: tags + properties: { + resource: { id: databaseName } + } + + resource list 'collections' = [for collection in collections: { + name: collection.name + properties: { + resource: { + id: collection.id + shardKey: { _id: collection.shardKey } + indexes: [ { key: { keys: [ collection.indexKey ] } } ] + } + } + }] + + dependsOn: [ + cosmos + ] +} + +output connectionStringKey string = connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-account.bicep b/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-account.bicep new file mode 100644 index 00000000..8431135e --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-account.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + tags: tags + keyVaultName: keyVaultName + kind: 'GlobalDocumentDB' + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id +output name string = cosmos.outputs.name diff --git a/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-db.bicep b/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-db.bicep new file mode 100644 index 00000000..265880dc --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-db.bicep @@ -0,0 +1,74 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param containers array = [] +param keyVaultName string +param principalIds array = [] + +module cosmos 'cosmos-sql-account.bicep' = { + name: 'cosmos-sql-account' + params: { + name: accountName + location: location + tags: tags + keyVaultName: keyVaultName + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = { + name: '${accountName}/${databaseName}' + properties: { + resource: { id: databaseName } + } + + resource list 'containers' = [for container in containers: { + name: container.name + properties: { + resource: { + id: container.id + partitionKey: { paths: [ container.partitionKey ] } + } + options: {} + } + }] + + dependsOn: [ + cosmos + ] +} + +module roleDefinition 'cosmos-sql-role-def.bicep' = { + name: 'cosmos-sql-role-definition' + params: { + accountName: accountName + } + dependsOn: [ + cosmos + database + ] +} + +// We need batchSize(1) here because sql role assignments have to be done sequentially +@batchSize(1) +module userRole 'cosmos-sql-role-assign.bicep' = [for principalId in principalIds: if (!empty(principalId)) { + name: 'cosmos-sql-user-role-${uniqueString(principalId)}' + params: { + accountName: accountName + roleDefinitionId: roleDefinition.outputs.id + principalId: principalId + } + dependsOn: [ + cosmos + database + ] +}] + +output accountId string = cosmos.outputs.id +output accountName string = cosmos.outputs.name +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint +output roleDefinitionId string = roleDefinition.outputs.id diff --git a/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-role-assign.bicep b/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-role-assign.bicep new file mode 100644 index 00000000..3949efef --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-role-assign.bicep @@ -0,0 +1,19 @@ +metadata description = 'Creates a SQL role assignment under an Azure Cosmos DB account.' +param accountName string + +param roleDefinitionId string +param principalId string = '' + +resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { + parent: cosmos + name: guid(roleDefinitionId, principalId, cosmos.id) + properties: { + principalId: principalId + roleDefinitionId: roleDefinitionId + scope: cosmos.id + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} diff --git a/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-role-def.bicep b/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-role-def.bicep new file mode 100644 index 00000000..778d6dc4 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/database/cosmos/sql/cosmos-sql-role-def.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates a SQL role definition under an Azure Cosmos DB account.' +param accountName string + +resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2022-08-15' = { + parent: cosmos + name: guid(cosmos.id, accountName, 'sql-role') + properties: { + assignableScopes: [ + cosmos.id + ] + permissions: [ + { + dataActions: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + ] + notDataActions: [] + } + ] + roleName: 'Reader Writer' + type: 'CustomRole' + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} + +output id string = roleDefinition.id diff --git a/Environments/App-Base-WebApp-AKS/core/database/postgresql/flexibleserver.bicep b/Environments/App-Base-WebApp-AKS/core/database/postgresql/flexibleserver.bicep new file mode 100644 index 00000000..7e26b1a8 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/database/postgresql/flexibleserver.bicep @@ -0,0 +1,65 @@ +metadata description = 'Creates an Azure Database for PostgreSQL - Flexible Server.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object +param storage object +param administratorLogin string +@secure() +param administratorLoginPassword string +param databaseNames array = [] +param allowAzureIPsFirewall bool = false +param allowAllIPsFirewall bool = false +param allowedSingleIPs array = [] + +// PostgreSQL version +param version string + +// Latest official version 2022-12-01 does not have Bicep types available +resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = { + location: location + tags: tags + name: name + sku: sku + properties: { + version: version + administratorLogin: administratorLogin + administratorLoginPassword: administratorLoginPassword + storage: storage + highAvailability: { + mode: 'Disabled' + } + } + + resource database 'databases' = [for name in databaseNames: { + name: name + }] + + resource firewall_all 'firewallRules' = if (allowAllIPsFirewall) { + name: 'allow-all-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '255.255.255.255' + } + } + + resource firewall_azure 'firewallRules' = if (allowAzureIPsFirewall) { + name: 'allow-all-azure-internal-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '0.0.0.0' + } + } + + resource firewall_single 'firewallRules' = [for ip in allowedSingleIPs: { + name: 'allow-single-${replace(ip, '.', '')}' + properties: { + startIpAddress: ip + endIpAddress: ip + } + }] + +} + +output POSTGRES_DOMAIN_NAME string = postgresServer.properties.fullyQualifiedDomainName diff --git a/Environments/App-Base-WebApp-AKS/core/database/sqlserver/sqlserver.bicep b/Environments/App-Base-WebApp-AKS/core/database/sqlserver/sqlserver.bicep new file mode 100644 index 00000000..84f2cc2c --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/database/sqlserver/sqlserver.bicep @@ -0,0 +1,130 @@ +metadata description = 'Creates an Azure SQL Server instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param appUser string = 'appUser' +param databaseName string +param keyVaultName string +param sqlAdmin string = 'sqlAdmin' +param connectionStringKey string = 'AZURE-SQL-CONNECTION-STRING' + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name + location: location + tags: tags + properties: { + version: '12.0' + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + administratorLogin: sqlAdmin + administratorLoginPassword: sqlAdminPassword + } + + resource database 'databases' = { + name: databaseName + location: location + } + + resource firewall 'firewallRules' = { + name: 'Azure Services' + properties: { + // Allow all clients + // Note: range [0.0.0.0-0.0.0.0] means "allow all Azure-hosted clients only". + // This is not sufficient, because we also want to allow direct access from developer machine, for debugging purposes. + startIpAddress: '0.0.0.1' + endIpAddress: '255.255.255.254' + } + } +} + +resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: '${name}-deployment-script' + location: location + kind: 'AzureCLI' + properties: { + azCliVersion: '2.37.0' + retentionInterval: 'PT1H' // Retain the script resource for 1 hour after it ends running + timeout: 'PT5M' // Five minutes + cleanupPreference: 'OnSuccess' + environmentVariables: [ + { + name: 'APPUSERNAME' + value: appUser + } + { + name: 'APPUSERPASSWORD' + secureValue: appUserPassword + } + { + name: 'DBNAME' + value: databaseName + } + { + name: 'DBSERVER' + value: sqlServer.properties.fullyQualifiedDomainName + } + { + name: 'SQLCMDPASSWORD' + secureValue: sqlAdminPassword + } + { + name: 'SQLADMIN' + value: sqlAdmin + } + ] + + scriptContent: ''' +wget https://github.com/microsoft/go-sqlcmd/releases/download/v0.8.1/sqlcmd-v0.8.1-linux-x64.tar.bz2 +tar x -f sqlcmd-v0.8.1-linux-x64.tar.bz2 -C . + +cat < ./initDb.sql +drop user if exists ${APPUSERNAME} +go +create user ${APPUSERNAME} with password = '${APPUSERPASSWORD}' +go +alter role db_owner add member ${APPUSERNAME} +go +SCRIPT_END + +./sqlcmd -S ${DBSERVER} -d ${DBNAME} -U ${SQLADMIN} -i ./initDb.sql + ''' + } +} + +resource sqlAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'sqlAdminPassword' + properties: { + value: sqlAdminPassword + } +} + +resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'appUserPassword' + properties: { + value: appUserPassword + } +} + +resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: '${connectionString}; Password=${appUserPassword}' + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output connectionStringKey string = connectionStringKey +output databaseName string = sqlServer::database.name diff --git a/Environments/App-Base-WebApp-AKS/core/gateway/apim.bicep b/Environments/App-Base-WebApp-AKS/core/gateway/apim.bicep new file mode 100644 index 00000000..be7464f0 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/gateway/apim.bicep @@ -0,0 +1,79 @@ +metadata description = 'Creates an Azure API Management instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The email address of the owner of the service') +@minLength(1) +param publisherEmail string = 'noreply@microsoft.com' + +@description('The name of the owner of the service') +@minLength(1) +param publisherName string = 'n/a' + +@description('The pricing tier of this API Management service') +@allowed([ + 'Consumption' + 'Developer' + 'Standard' + 'Premium' +]) +param sku string = 'Consumption' + +@description('The instance size of this API Management service.') +@allowed([ 0, 1, 2 ]) +param skuCount int = 0 + +@description('Azure Application Insights Name') +param applicationInsightsName string + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': name }) + sku: { + name: sku + capacity: (sku == 'Consumption') ? 0 : ((sku == 'Developer') ? 1 : skuCount) + } + properties: { + publisherEmail: publisherEmail + publisherName: publisherName + // Custom properties are not supported for Consumption SKU + customProperties: sku == 'Consumption' ? {} : { + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_GCM_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TripleDes168': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Ssl30': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30': 'false' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' = if (!empty(applicationInsightsName)) { + name: 'app-insights-logger' + parent: apimService + properties: { + credentials: { + instrumentationKey: applicationInsights.properties.InstrumentationKey + } + description: 'Logger to Azure Application Insights' + isBuffered: false + loggerType: 'applicationInsights' + resourceId: applicationInsights.id + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output apimServiceName string = apimService.name diff --git a/Environments/App-Base-WebApp-AKS/core/host/aks-agent-pool.bicep b/Environments/App-Base-WebApp-AKS/core/host/aks-agent-pool.bicep new file mode 100644 index 00000000..9c764358 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/aks-agent-pool.bicep @@ -0,0 +1,18 @@ +metadata description = 'Adds an agent pool to an Azure Kubernetes Service (AKS) cluster.' +param clusterName string + +@description('The agent pool name') +param name string + +@description('The agent pool configuration') +param config object + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} + +resource nodePool 'Microsoft.ContainerService/managedClusters/agentPools@2023-10-02-preview' = { + parent: aksCluster + name: name + properties: config +} diff --git a/Environments/App-Base-WebApp-AKS/core/host/aks-managed-cluster.bicep b/Environments/App-Base-WebApp-AKS/core/host/aks-managed-cluster.bicep new file mode 100644 index 00000000..de562a66 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/aks-managed-cluster.bicep @@ -0,0 +1,140 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// AAD Integration +@description('Enable Azure Active Directory integration') +param enableAad bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('Configuration of AKS add-ons') +param addOns object = {} + +@description('The log analytics workspace id used for logging & monitoring') +param workspaceId string = '' + +@description('The node pool configuration for the System agent pool') +param systemPoolConfig object + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +resource aks 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' = { + name: name + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + sku: { + name: 'Base' + tier: sku + } + properties: { + nodeResourceGroup: !empty(nodeResourceGroupName) ? nodeResourceGroupName : 'rg-mc-${name}' + kubernetesVersion: kubernetesVersion + dnsPrefix: empty(dnsPrefix) ? '${name}-dns' : dnsPrefix + enableRBAC: enableRbac + aadProfile: enableAad ? { + managed: true + enableAzureRBAC: enableAzureRbac + tenantID: aadTenantId + } : null + agentPoolProfiles: [ + systemPoolConfig + ] + networkProfile: { + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } + disableLocalAccounts: disableLocalAccounts && enableAad + addonProfiles: addOns + ingressProfile: { + webAppRouting: { + enabled: webAppRoutingAddon + } + } + } +} + +var aksDiagCategories = [ + 'cluster-autoscaler' + 'kube-controller-manager' + 'kube-audit-admin' + 'guard' +] + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'aks-diagnostics' + scope: aks + properties: { + workspaceId: workspaceId + logs: [for category in aksDiagCategories: { + category: category + enabled: true + }] + metrics: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +} + +@description('The resource name of the AKS cluster') +output clusterName string = aks.name + +@description('The AKS cluster identity') +output clusterIdentity object = { + clientId: aks.properties.identityProfile.kubeletidentity.clientId + objectId: aks.properties.identityProfile.kubeletidentity.objectId + resourceId: aks.properties.identityProfile.kubeletidentity.resourceId +} diff --git a/Environments/App-Base-WebApp-AKS/core/host/aks.bicep b/Environments/App-Base-WebApp-AKS/core/host/aks.bicep new file mode 100644 index 00000000..536a534b --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/aks.bicep @@ -0,0 +1,280 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool as well as an additional user agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name for the Azure container registry (ACR)') +param containerRegistryName string + +@description('The name of the connected log analytics workspace') +param logAnalyticsName string = '' + +@description('The name of the keyvault to grant access') +param keyVaultName string + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('AKS add-ons configuration') +param addOns object = { + azurePolicy: { + enabled: true + config: { + version: 'v2' + } + } + keyVault: { + enabled: true + config: { + enableSecretRotation: 'true' + rotationPollInterval: '2m' + } + } + openServiceMesh: { + enabled: false + config: {} + } + omsAgent: { + enabled: true + config: {} + } + applicationGateway: { + enabled: false + config: {} + } +} + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@allowed([ + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The System Pool Preset sizing') +param systemPoolType string = 'CostOptimised' + +@allowed([ + '' + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The User Pool Preset sizing') +param agentPoolType string = '' + +// Configure system / user agent pools +@description('Custom configuration of system node pool') +param systemPoolConfig object = {} +@description('Custom configuration of user node pool') +param agentPoolConfig object = {} + +@description('Id of the user or app to assign application roles') +param principalId string = '' + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// Configure AKS add-ons +var omsAgentConfig = (!empty(logAnalyticsName) && !empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? union( + addOns.omsAgent, + { + config: { + logAnalyticsWorkspaceResourceID: logAnalytics.id + } + } +) : {} + +var addOnsConfig = union( + (!empty(addOns.azurePolicy) && addOns.azurePolicy.enabled) ? { azurepolicy: addOns.azurePolicy } : {}, + (!empty(addOns.keyVault) && addOns.keyVault.enabled) ? { azureKeyvaultSecretsProvider: addOns.keyVault } : {}, + (!empty(addOns.openServiceMesh) && addOns.openServiceMesh.enabled) ? { openServiceMesh: addOns.openServiceMesh } : {}, + (!empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? { omsagent: omsAgentConfig } : {}, + (!empty(addOns.applicationGateway) && addOns.applicationGateway.enabled) ? { ingressApplicationGateway: addOns.applicationGateway } : {} +) + +// Link to existing log analytics workspace when available +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' existing = if (!empty(logAnalyticsName)) { + name: logAnalyticsName +} + +var systemPoolSpec = !empty(systemPoolConfig) ? systemPoolConfig : nodePoolPresets[systemPoolType] + +// Create the primary AKS cluster resources and system node pool +module managedCluster 'aks-managed-cluster.bicep' = { + name: 'managed-cluster' + params: { + name: name + location: location + tags: tags + systemPoolConfig: union( + { name: 'npsystem', mode: 'System' }, + nodePoolBase, + systemPoolSpec + ) + nodeResourceGroupName: nodeResourceGroupName + sku: sku + dnsPrefix: dnsPrefix + kubernetesVersion: kubernetesVersion + addOns: addOnsConfig + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + enableAad: enableAzureRbac && aadTenantId != '' + disableLocalAccounts: disableLocalAccounts + aadTenantId: aadTenantId + enableRbac: enableRbac + enableAzureRbac: enableAzureRbac + webAppRoutingAddon: webAppRoutingAddon + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } +} + +var hasAgentPool = !empty(agentPoolConfig) || !empty(agentPoolType) +var agentPoolSpec = hasAgentPool && !empty(agentPoolConfig) ? agentPoolConfig : empty(agentPoolType) ? {} : nodePoolPresets[agentPoolType] + +// Create additional user agent pool when specified +module agentPool 'aks-agent-pool.bicep' = if (hasAgentPool) { + name: 'aks-node-pool' + params: { + clusterName: managedCluster.outputs.clusterName + name: 'npuserpool' + config: union({ name: 'npuser', mode: 'User' }, nodePoolBase, agentPoolSpec) + } +} + +// Creates container registry (ACR) +module containerRegistry 'container-registry.bicep' = { + name: 'container-registry' + params: { + name: containerRegistryName + location: location + tags: tags + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + } +} + +// Grant ACR Pull access from cluster managed identity to container registry +module containerRegistryAccess '../security/registry-access.bicep' = { + name: 'cluster-container-registry-access' + params: { + containerRegistryName: containerRegistry.outputs.name + principalId: managedCluster.outputs.clusterIdentity.objectId + } +} + +// Give AKS cluster access to the specified principal +module clusterAccess '../security/aks-managed-cluster-access.bicep' = if (enableAzureRbac || disableLocalAccounts) { + name: 'cluster-access' + params: { + clusterName: managedCluster.outputs.clusterName + principalId: principalId + } +} + +// Give the AKS Cluster access to KeyVault +module clusterKeyVaultAccess '../security/keyvault-access.bicep' = { + name: 'cluster-keyvault-access' + params: { + keyVaultName: keyVaultName + principalId: managedCluster.outputs.clusterIdentity.objectId + } +} + +// Helpers for node pool configuration +var nodePoolBase = { + osType: 'Linux' + maxPods: 30 + type: 'VirtualMachineScaleSets' + upgradeSettings: { + maxSurge: '33%' + } +} + +var nodePoolPresets = { + CostOptimised: { + vmSize: 'Standard_B4ms' + count: 1 + minCount: 1 + maxCount: 3 + enableAutoScaling: true + availabilityZones: [] + } + Standard: { + vmSize: 'Standard_DS2_v2' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } + HighSpec: { + vmSize: 'Standard_D4s_v3' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } +} + +// Module outputs +@description('The resource name of the AKS cluster') +output clusterName string = managedCluster.outputs.clusterName + +@description('The AKS cluster identity') +output clusterIdentity object = managedCluster.outputs.clusterIdentity + +@description('The resource name of the ACR') +output containerRegistryName string = containerRegistry.outputs.name + +@description('The login server for the container registry') +output containerRegistryLoginServer string = containerRegistry.outputs.loginServer diff --git a/Environments/App-Base-WebApp-AKS/core/host/appservice-appsettings.bicep b/Environments/App-Base-WebApp-AKS/core/host/appservice-appsettings.bicep new file mode 100644 index 00000000..f4b22f81 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/appservice-appsettings.bicep @@ -0,0 +1,17 @@ +metadata description = 'Updates app settings for an Azure App Service.' +@description('The name of the app service resource within the current resource group scope') +param name string + +@description('The app settings to be applied to the app service') +@secure() +param appSettings object + +resource appService 'Microsoft.Web/sites@2022-03-01' existing = { + name: name +} + +resource settings 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'appsettings' + parent: appService + properties: appSettings +} diff --git a/Environments/App-Base-WebApp-AKS/core/host/appservice.bicep b/Environments/App-Base-WebApp-AKS/core/host/appservice.bicep new file mode 100644 index 00000000..bef4d2ba --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/appservice.bicep @@ -0,0 +1,123 @@ +metadata description = 'Creates an Azure App Service in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Microsoft.Web/sites Properties +param kind string = 'app,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = false +param use32BitWorkerProcess bool = false +param ftpsState string = 'FtpsOnly' +param healthCheckPath string = '' + +resource appService 'Microsoft.Web/sites@2022-03-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + serverFarmId: appServicePlanId + siteConfig: { + linuxFxVersion: linuxFxVersion + alwaysOn: alwaysOn + ftpsState: ftpsState + minTlsVersion: '1.2' + appCommandLine: appCommandLine + numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null + minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null + use32BitWorkerProcess: use32BitWorkerProcess + functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null + healthCheckPath: healthCheckPath + cors: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: true + } + + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + + resource basicPublishingCredentialsPoliciesFtp 'basicPublishingCredentialsPolicies' = { + name: 'ftp' + properties: { + allow: false + } + } + + resource basicPublishingCredentialsPoliciesScm 'basicPublishingCredentialsPolicies' = { + name: 'scm' + properties: { + allow: false + } + } +} + +// Updates to the single Microsoft.sites/web/config resources that need to be performed sequentially +// sites/web/config 'appsettings' +module configAppSettings 'appservice-appsettings.bicep' = { + name: '${name}-appSettings' + params: { + name: appService.name + appSettings: union(appSettings, + { + SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) + ENABLE_ORYX_BUILD: string(enableOryxBuild) + }, + runtimeName == 'python' && appCommandLine == '' ? { PYTHON_ENABLE_GUNICORN_MULTIWORKERS: 'true'} : {}, + !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, + !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) + } +} + +// sites/web/config 'logs' +resource configLogs 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'logs' + parent: appService + properties: { + applicationLogs: { fileSystem: { level: 'Verbose' } } + detailedErrorMessages: { enabled: true } + failedRequestsTracing: { enabled: true } + httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } + } + dependsOn: [configAppSettings] +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) { + name: keyVaultName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output identityPrincipalId string = managedIdentity ? appService.identity.principalId : '' +output name string = appService.name +output uri string = 'https://${appService.properties.defaultHostName}' diff --git a/Environments/App-Base-WebApp-AKS/core/host/appserviceplan.bicep b/Environments/App-Base-WebApp-AKS/core/host/appserviceplan.bicep new file mode 100644 index 00000000..2e37e041 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/appserviceplan.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param kind string = '' +param reserved bool = true +param sku object + +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + kind: kind + properties: { + reserved: reserved + } +} + +output id string = appServicePlan.id +output name string = appServicePlan.name diff --git a/Environments/App-Base-WebApp-AKS/core/host/container-app-upsert.bicep b/Environments/App-Base-WebApp-AKS/core/host/container-app-upsert.bicep new file mode 100644 index 00000000..3eec62f2 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/container-app-upsert.bicep @@ -0,0 +1,105 @@ +metadata description = 'Creates or updates an existing Azure Container App.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The environment name for the container apps') +param containerAppsEnvironmentName string + +@description('The number of CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('The amount of memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@allowed([ 'http', 'grpc' ]) +@description('The protocol used by Dapr to connect to the app, e.g., HTTP or gRPC') +param daprAppProtocol string = 'http' + +@description('Enable or disable Dapr for the container app') +param daprEnabled bool = false + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Specifies if the resource already exists') +param exists bool = false + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The name of the container image') +param imageName string = '' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The target port for the container') +param targetPort int = 80 + +resource existingApp 'Microsoft.App/containerApps@2023-04-01-preview' existing = if (exists) { + name: name +} + +module app 'container-app.bicep' = { + name: '${deployment().name}-update' + params: { + name: name + location: location + tags: tags + identityType: identityType + identityName: identityName + ingressEnabled: ingressEnabled + containerName: containerName + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: containerCpuCoreCount + containerMemory: containerMemory + containerMinReplicas: containerMinReplicas + containerMaxReplicas: containerMaxReplicas + daprEnabled: daprEnabled + daprAppId: daprAppId + daprAppProtocol: daprAppProtocol + secrets: secrets + external: external + env: env + imageName: !empty(imageName) ? imageName : exists ? existingApp.properties.template.containers[0].image : '' + targetPort: targetPort + serviceBinds: serviceBinds + } +} + +output defaultDomain string = app.outputs.defaultDomain +output imageName string = app.outputs.imageName +output name string = app.outputs.name +output uri string = app.outputs.uri diff --git a/Environments/App-Base-WebApp-AKS/core/host/container-app.bicep b/Environments/App-Base-WebApp-AKS/core/host/container-app.bicep new file mode 100644 index 00000000..3724086d --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/container-app.bicep @@ -0,0 +1,162 @@ +metadata description = 'Creates a container app in an Azure Container App environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Allowed origins') +param allowedOrigins array = [] + +@description('Name of the environment for container apps') +param containerAppsEnvironmentName string + +@description('CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('Memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@description('The protocol used by Dapr to connect to the app, e.g., http or grpc') +@allowed([ 'http', 'grpc' ]) +param daprAppProtocol string = 'http' + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Enable Dapr') +param daprEnabled bool = false + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the container image') +param imageName string = '' + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +param revisionMode string = 'Single' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The name of the container apps add-on to use. e.g. redis') +param serviceType string = '' + +@description('The target port for the container') +param targetPort int = 80 + +resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(identityName)) { + name: identityName +} + +// Private registry support requires both an ACR name and a User Assigned managed identity +var usePrivateRegistry = !empty(identityName) && !empty(containerRegistryName) + +// Automatically set to `UserAssigned` when an `identityName` has been set +var normalizedIdentityType = !empty(identityName) ? 'UserAssigned' : identityType + +module containerRegistryAccess '../security/registry-access.bicep' = if (usePrivateRegistry) { + name: '${deployment().name}-registry-access' + params: { + containerRegistryName: containerRegistryName + principalId: usePrivateRegistry ? userIdentity.properties.principalId : '' + } +} + +resource app 'Microsoft.App/containerApps@2023-04-01-preview' = { + name: name + location: location + tags: tags + // It is critical that the identity is granted ACR pull access before the app is created + // otherwise the container app will throw a provision error + // This also forces us to use an user assigned managed identity since there would no way to + // provide the system assigned identity with the ACR pull access before the app is created + dependsOn: usePrivateRegistry ? [ containerRegistryAccess ] : [] + identity: { + type: normalizedIdentityType + userAssignedIdentities: !empty(identityName) && normalizedIdentityType == 'UserAssigned' ? { '${userIdentity.id}': {} } : null + } + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + activeRevisionsMode: revisionMode + ingress: ingressEnabled ? { + external: external + targetPort: targetPort + transport: 'auto' + corsPolicy: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } : null + dapr: daprEnabled ? { + enabled: true + appId: daprAppId + appProtocol: daprAppProtocol + appPort: ingressEnabled ? targetPort : 0 + } : { enabled: false } + secrets: secrets + service: !empty(serviceType) ? { type: serviceType } : null + registries: usePrivateRegistry ? [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userIdentity.id + } + ] : [] + } + template: { + serviceBinds: !empty(serviceBinds) ? serviceBinds : null + containers: [ + { + image: !empty(imageName) ? imageName : 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: containerName + env: env + resources: { + cpu: json(containerCpuCoreCount) + memory: containerMemory + } + } + ] + scale: { + minReplicas: containerMinReplicas + maxReplicas: containerMaxReplicas + } + } + } +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' existing = { + name: containerAppsEnvironmentName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output identityPrincipalId string = normalizedIdentityType == 'None' ? '' : (empty(identityName) ? app.identity.principalId : userIdentity.properties.principalId) +output imageName string = imageName +output name string = app.name +output serviceBind object = !empty(serviceType) ? { serviceId: app.id, name: name } : {} +output uri string = ingressEnabled ? 'https://${app.properties.configuration.ingress.fqdn}' : '' diff --git a/Environments/App-Base-WebApp-AKS/core/host/container-apps-environment.bicep b/Environments/App-Base-WebApp-AKS/core/host/container-apps-environment.bicep new file mode 100644 index 00000000..8633ba48 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/container-apps-environment.bicep @@ -0,0 +1,41 @@ +metadata description = 'Creates an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the Application Insights resource') +param applicationInsightsName string = '' + +@description('Specifies if Dapr is enabled') +param daprEnabled bool = false + +@description('Name of the Log Analytics workspace') +param logAnalyticsWorkspaceName string + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: logAnalyticsWorkspaceName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output id string = containerAppsEnvironment.id +output name string = containerAppsEnvironment.name diff --git a/Environments/App-Base-WebApp-AKS/core/host/container-apps.bicep b/Environments/App-Base-WebApp-AKS/core/host/container-apps.bicep new file mode 100644 index 00000000..1c656e28 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/container-apps.bicep @@ -0,0 +1,40 @@ +metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param containerRegistryName string +param containerRegistryResourceGroupName string = '' +param containerRegistryAdminUserEnabled bool = false +param logAnalyticsWorkspaceName string +param applicationInsightsName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${name}-container-apps-environment' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + logAnalyticsWorkspaceName: logAnalyticsWorkspaceName + applicationInsightsName: applicationInsightsName + } +} + +module containerRegistry 'container-registry.bicep' = { + name: '${name}-container-registry' + scope: !empty(containerRegistryResourceGroupName) ? resourceGroup(containerRegistryResourceGroupName) : resourceGroup() + params: { + name: containerRegistryName + location: location + adminUserEnabled: containerRegistryAdminUserEnabled + tags: tags + } +} + +output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output environmentName string = containerAppsEnvironment.outputs.name +output environmentId string = containerAppsEnvironment.outputs.id + +output registryLoginServer string = containerRegistry.outputs.loginServer +output registryName string = containerRegistry.outputs.name diff --git a/Environments/App-Base-WebApp-AKS/core/host/container-registry.bicep b/Environments/App-Base-WebApp-AKS/core/host/container-registry.bicep new file mode 100644 index 00000000..9c64531b --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/container-registry.bicep @@ -0,0 +1,83 @@ +metadata description = 'Creates an Azure Container Registry.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Indicates whether admin user is enabled') +param adminUserEnabled bool = false + +@description('Indicates whether anonymous pull is enabled') +param anonymousPullEnabled bool = false + +@description('Indicates whether data endpoint is enabled') +param dataEndpointEnabled bool = false + +@description('Encryption settings') +param encryption object = { + status: 'disabled' +} + +@description('Options for bypassing network rules') +param networkRuleBypassOptions string = 'AzureServices' + +@description('Public network access setting') +param publicNetworkAccess string = 'Enabled' + +@description('SKU settings') +param sku object = { + name: 'Basic' +} + +@description('Zone redundancy setting') +param zoneRedundancy string = 'Disabled' + +@description('The log analytics workspace ID used for logging and monitoring') +param workspaceId string = '' + +// 2022-02-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { + name: name + location: location + tags: tags + sku: sku + properties: { + adminUserEnabled: adminUserEnabled + anonymousPullEnabled: anonymousPullEnabled + dataEndpointEnabled: dataEndpointEnabled + encryption: encryption + networkRuleBypassOptions: networkRuleBypassOptions + publicNetworkAccess: publicNetworkAccess + zoneRedundancy: zoneRedundancy + } +} + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'registry-diagnostics' + scope: containerRegistry + properties: { + workspaceId: workspaceId + logs: [ + { + category: 'ContainerRegistryRepositoryEvents' + enabled: true + } + { + category: 'ContainerRegistryLoginEvents' + enabled: true + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + timeGrain: 'PT1M' + } + ] + } +} + +output loginServer string = containerRegistry.properties.loginServer +output name string = containerRegistry.name diff --git a/Environments/App-Base-WebApp-AKS/core/host/functions.bicep b/Environments/App-Base-WebApp-AKS/core/host/functions.bicep new file mode 100644 index 00000000..7070a2c6 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/functions.bicep @@ -0,0 +1,86 @@ +metadata description = 'Creates an Azure Function in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) +param storageAccountName string + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Function Settings +@allowed([ + '~4', '~3', '~2', '~1' +]) +param extensionVersion string = '~4' + +// Microsoft.Web/sites Properties +param kind string = 'functionapp,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = true +param use32BitWorkerProcess bool = false +param healthCheckPath string = '' + +module functions 'appservice.bicep' = { + name: '${name}-functions' + params: { + name: name + location: location + tags: tags + allowedOrigins: allowedOrigins + alwaysOn: alwaysOn + appCommandLine: appCommandLine + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: union(appSettings, { + AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${storage.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' + FUNCTIONS_EXTENSION_VERSION: extensionVersion + FUNCTIONS_WORKER_RUNTIME: runtimeName + }) + clientAffinityEnabled: clientAffinityEnabled + enableOryxBuild: enableOryxBuild + functionAppScaleLimit: functionAppScaleLimit + healthCheckPath: healthCheckPath + keyVaultName: keyVaultName + kind: kind + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + minimumElasticInstanceCount: minimumElasticInstanceCount + numberOfWorkers: numberOfWorkers + runtimeName: runtimeName + runtimeVersion: runtimeVersion + runtimeNameAndVersion: runtimeNameAndVersion + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + use32BitWorkerProcess: use32BitWorkerProcess + } +} + +resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { + name: storageAccountName +} + +output identityPrincipalId string = managedIdentity ? functions.outputs.identityPrincipalId : '' +output name string = functions.outputs.name +output uri string = functions.outputs.uri diff --git a/Environments/App-Base-WebApp-AKS/core/host/staticwebapp.bicep b/Environments/App-Base-WebApp-AKS/core/host/staticwebapp.bicep new file mode 100644 index 00000000..cedaf906 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/host/staticwebapp.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Static Web Apps instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'Free' + tier: 'Free' +} + +resource web 'Microsoft.Web/staticSites@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + properties: { + provider: 'Custom' + } +} + +output name string = web.name +output uri string = 'https://${web.properties.defaultHostname}' diff --git a/Environments/App-Base-WebApp-AKS/core/monitor/applicationinsights-dashboard.bicep b/Environments/App-Base-WebApp-AKS/core/monitor/applicationinsights-dashboard.bicep new file mode 100644 index 00000000..d082e668 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/monitor/applicationinsights-dashboard.bicep @@ -0,0 +1,1236 @@ +metadata description = 'Creates a dashboard for an Application Insights instance.' +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} diff --git a/Environments/App-Base-WebApp-AKS/core/monitor/applicationinsights.bicep b/Environments/App-Base-WebApp-AKS/core/monitor/applicationinsights.bicep new file mode 100644 index 00000000..4b4d01e3 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/monitor/applicationinsights.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' +param name string +param dashboardName string = '' +param location string = resourceGroup().location +param tags object = {} +param logAnalyticsWorkspaceId string + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { + name: 'application-insights-dashboard' + params: { + name: dashboardName + location: location + applicationInsightsName: applicationInsights.name + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/Environments/App-Base-WebApp-AKS/core/monitor/loganalytics.bicep b/Environments/App-Base-WebApp-AKS/core/monitor/loganalytics.bicep new file mode 100644 index 00000000..33f9dc29 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/monitor/loganalytics.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates a Log Analytics workspace.' +param name string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: name + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/Environments/App-Base-WebApp-AKS/core/monitor/monitoring.bicep b/Environments/App-Base-WebApp-AKS/core/monitor/monitoring.bicep new file mode 100644 index 00000000..6bb05b0b --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/monitor/monitoring.bicep @@ -0,0 +1,32 @@ +metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string = '' +param location string = resourceGroup().location +param tags object = {} + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + name: logAnalyticsName + location: location + tags: tags + } +} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/Environments/App-Base-WebApp-AKS/core/networking/cdn-endpoint.bicep b/Environments/App-Base-WebApp-AKS/core/networking/cdn-endpoint.bicep new file mode 100644 index 00000000..5e8ab695 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/networking/cdn-endpoint.bicep @@ -0,0 +1,52 @@ +metadata description = 'Adds an endpoint to an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The name of the CDN profile resource') +@minLength(1) +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('The origin URL for the endpoint') +@minLength(1) +param originUrl string + +resource endpoint 'Microsoft.Cdn/profiles/endpoints@2022-05-01-preview' = { + parent: cdnProfile + name: name + location: location + tags: tags + properties: { + originHostHeader: originUrl + isHttpAllowed: false + isHttpsAllowed: true + queryStringCachingBehavior: 'UseQueryString' + optimizationType: 'GeneralWebDelivery' + origins: [ + { + name: replace(originUrl, '.', '-') + properties: { + hostName: originUrl + originHostHeader: originUrl + priority: 1 + weight: 1000 + enabled: true + } + } + ] + deliveryPolicy: { + rules: deliveryPolicyRules + } + } +} + +resource cdnProfile 'Microsoft.Cdn/profiles@2022-05-01-preview' existing = { + name: cdnProfileName +} + +output id string = endpoint.id +output name string = endpoint.name +output uri string = 'https://${endpoint.properties.hostName}' diff --git a/Environments/App-Base-WebApp-AKS/core/networking/cdn-profile.bicep b/Environments/App-Base-WebApp-AKS/core/networking/cdn-profile.bicep new file mode 100644 index 00000000..27669ee2 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/networking/cdn-profile.bicep @@ -0,0 +1,34 @@ +metadata description = 'Creates an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The pricing tier of this CDN profile') +@allowed([ + 'Custom_Verizon' + 'Premium_AzureFrontDoor' + 'Premium_Verizon' + 'StandardPlus_955BandWidth_ChinaCdn' + 'StandardPlus_AvgBandWidth_ChinaCdn' + 'StandardPlus_ChinaCdn' + 'Standard_955BandWidth_ChinaCdn' + 'Standard_Akamai' + 'Standard_AvgBandWidth_ChinaCdn' + 'Standard_AzureFrontDoor' + 'Standard_ChinaCdn' + 'Standard_Microsoft' + 'Standard_Verizon' +]) +param sku string = 'Standard_Microsoft' + +resource profile 'Microsoft.Cdn/profiles@2022-05-01-preview' = { + name: name + location: location + tags: tags + sku: { + name: sku + } +} + +output id string = profile.id +output name string = profile.name diff --git a/Environments/App-Base-WebApp-AKS/core/networking/cdn.bicep b/Environments/App-Base-WebApp-AKS/core/networking/cdn.bicep new file mode 100644 index 00000000..de98a1f9 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/networking/cdn.bicep @@ -0,0 +1,42 @@ +metadata description = 'Creates an Azure CDN profile with a single endpoint.' +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the CDN endpoint resource') +param cdnEndpointName string + +@description('Name of the CDN profile resource') +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('Origin URL for the CDN endpoint') +param originUrl string + +module cdnProfile 'cdn-profile.bicep' = { + name: 'cdn-profile' + params: { + name: cdnProfileName + location: location + tags: tags + } +} + +module cdnEndpoint 'cdn-endpoint.bicep' = { + name: 'cdn-endpoint' + params: { + name: cdnEndpointName + location: location + tags: tags + cdnProfileName: cdnProfile.outputs.name + originUrl: originUrl + deliveryPolicyRules: deliveryPolicyRules + } +} + +output endpointName string = cdnEndpoint.outputs.name +output endpointId string = cdnEndpoint.outputs.id +output profileName string = cdnProfile.outputs.name +output profileId string = cdnProfile.outputs.id +output uri string = cdnEndpoint.outputs.uri diff --git a/Environments/App-Base-WebApp-AKS/core/search/search-services.bicep b/Environments/App-Base-WebApp-AKS/core/search/search-services.bicep new file mode 100644 index 00000000..d9c619a9 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/search/search-services.bicep @@ -0,0 +1,68 @@ +metadata description = 'Creates an Azure AI Search instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'standard' +} + +param authOptions object = {} +param disableLocalAuth bool = false +param disabledDataExfiltrationOptions array = [] +param encryptionWithCmk object = { + enforcement: 'Unspecified' +} +@allowed([ + 'default' + 'highDensity' +]) +param hostingMode string = 'default' +param networkRuleSet object = { + bypass: 'None' + ipRules: [] +} +param partitionCount int = 1 +@allowed([ + 'enabled' + 'disabled' +]) +param publicNetworkAccess string = 'enabled' +param replicaCount int = 1 +@allowed([ + 'disabled' + 'free' + 'standard' +]) +param semanticSearch string = 'disabled' + +var searchIdentityProvider = (sku.name == 'free') ? null : { + type: 'SystemAssigned' +} + +resource search 'Microsoft.Search/searchServices@2021-04-01-preview' = { + name: name + location: location + tags: tags + // The free tier does not support managed identity + identity: searchIdentityProvider + properties: { + authOptions: authOptions + disableLocalAuth: disableLocalAuth + disabledDataExfiltrationOptions: disabledDataExfiltrationOptions + encryptionWithCmk: encryptionWithCmk + hostingMode: hostingMode + networkRuleSet: networkRuleSet + partitionCount: partitionCount + publicNetworkAccess: publicNetworkAccess + replicaCount: replicaCount + semanticSearch: semanticSearch + } + sku: sku +} + +output id string = search.id +output endpoint string = 'https://${name}.search.windows.net/' +output name string = search.name +output principalId string = !empty(searchIdentityProvider) ? search.identity.principalId : '' + diff --git a/Environments/App-Base-WebApp-AKS/core/security/aks-managed-cluster-access.bicep b/Environments/App-Base-WebApp-AKS/core/security/aks-managed-cluster-access.bicep new file mode 100644 index 00000000..dec984e8 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/security/aks-managed-cluster-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns RBAC role to the specified AKS cluster and principal.' +param clusterName string +param principalId string + +var aksClusterAdminRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b') + +resource aksRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aksCluster // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, aksClusterAdminRole) + properties: { + roleDefinitionId: aksClusterAdminRole + principalType: 'User' + principalId: principalId + } +} + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} diff --git a/Environments/App-Base-WebApp-AKS/core/security/keyvault-access.bicep b/Environments/App-Base-WebApp-AKS/core/security/keyvault-access.bicep new file mode 100644 index 00000000..316775f2 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/security/keyvault-access.bicep @@ -0,0 +1,22 @@ +metadata description = 'Assigns an Azure Key Vault access policy.' +param name string = 'add' + +param keyVaultName string +param permissions object = { secrets: [ 'get', 'list' ] } +param principalId string + +resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = { + parent: keyVault + name: name + properties: { + accessPolicies: [ { + objectId: principalId + tenantId: subscription().tenantId + permissions: permissions + } ] + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/App-Base-WebApp-AKS/core/security/keyvault-secret.bicep b/Environments/App-Base-WebApp-AKS/core/security/keyvault-secret.bicep new file mode 100644 index 00000000..7441b296 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/security/keyvault-secret.bicep @@ -0,0 +1,31 @@ +metadata description = 'Creates or updates a secret in an Azure Key Vault.' +param name string +param tags object = {} +param keyVaultName string +param contentType string = 'string' +@description('The value of the secret. Provide only derived values like blob storage access, but do not hard code any secrets in your templates') +@secure() +param secretValue string + +param enabled bool = true +param exp int = 0 +param nbf int = 0 + +resource keyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + name: name + tags: tags + parent: keyVault + properties: { + attributes: { + enabled: enabled + exp: exp + nbf: nbf + } + contentType: contentType + value: secretValue + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/App-Base-WebApp-AKS/core/security/keyvault.bicep b/Environments/App-Base-WebApp-AKS/core/security/keyvault.bicep new file mode 100644 index 00000000..314a1db6 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/security/keyvault.bicep @@ -0,0 +1,26 @@ +metadata description = 'Creates an Azure Key Vault.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param principalId string = '' + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: name + location: location + tags: tags + properties: { + tenantId: subscription().tenantId + sku: { family: 'A', name: 'standard' } + accessPolicies: !empty(principalId) ? [ + { + objectId: principalId + permissions: { secrets: [ 'get', 'list' ] } + tenantId: subscription().tenantId + } + ] : [] + } +} + +output endpoint string = keyVault.properties.vaultUri +output name string = keyVault.name diff --git a/Environments/App-Base-WebApp-AKS/core/security/registry-access.bicep b/Environments/App-Base-WebApp-AKS/core/security/registry-access.bicep new file mode 100644 index 00000000..5335efab --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/security/registry-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns ACR Pull permissions to access an Azure Container Registry.' +param containerRegistryName string +param principalId string + +var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + +resource aksAcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: containerRegistry // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, acrPullRole) + properties: { + roleDefinitionId: acrPullRole + principalType: 'ServicePrincipal' + principalId: principalId + } +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { + name: containerRegistryName +} diff --git a/Environments/App-Base-WebApp-AKS/core/security/role.bicep b/Environments/App-Base-WebApp-AKS/core/security/role.bicep new file mode 100644 index 00000000..0b30cfd3 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/security/role.bicep @@ -0,0 +1,21 @@ +metadata description = 'Creates a role assignment for a service principal.' +param principalId string + +@allowed([ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' +]) +param principalType string = 'ServicePrincipal' +param roleDefinitionId string + +resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) + properties: { + principalId: principalId + principalType: principalType + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) + } +} diff --git a/Environments/App-Base-WebApp-AKS/core/storage/storage-account.bicep b/Environments/App-Base-WebApp-AKS/core/storage/storage-account.bicep new file mode 100644 index 00000000..4b6febbe --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/storage/storage-account.bicep @@ -0,0 +1,64 @@ +metadata description = 'Creates an Azure storage account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@allowed([ + 'Cool' + 'Hot' + 'Premium' ]) +param accessTier string = 'Hot' +param allowBlobPublicAccess bool = true +param allowCrossTenantReplication bool = true +param allowSharedKeyAccess bool = true +param containers array = [] +param defaultToOAuthAuthentication bool = false +param deleteRetentionPolicy object = {} +@allowed([ 'AzureDnsZone', 'Standard' ]) +param dnsEndpointType string = 'Standard' +param kind string = 'StorageV2' +param minimumTlsVersion string = 'TLS1_2' +param supportsHttpsTrafficOnly bool = true +param networkAcls object = { + bypass: 'AzureServices' + defaultAction: 'Allow' +} +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { name: 'Standard_LRS' } + +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { + name: name + location: location + tags: tags + kind: kind + sku: sku + properties: { + accessTier: accessTier + allowBlobPublicAccess: allowBlobPublicAccess + allowCrossTenantReplication: allowCrossTenantReplication + allowSharedKeyAccess: allowSharedKeyAccess + defaultToOAuthAuthentication: defaultToOAuthAuthentication + dnsEndpointType: dnsEndpointType + minimumTlsVersion: minimumTlsVersion + networkAcls: networkAcls + publicNetworkAccess: publicNetworkAccess + supportsHttpsTrafficOnly: supportsHttpsTrafficOnly + } + + resource blobServices 'blobServices' = if (!empty(containers)) { + name: 'default' + properties: { + deleteRetentionPolicy: deleteRetentionPolicy + } + resource container 'containers' = [for container in containers: { + name: container.name + properties: { + publicAccess: contains(container, 'publicAccess') ? container.publicAccess : 'None' + } + }] + } +} + +output name string = storage.name +output primaryEndpoints object = storage.properties.primaryEndpoints diff --git a/Environments/App-Base-WebApp-AKS/core/testing/loadtesting.bicep b/Environments/App-Base-WebApp-AKS/core/testing/loadtesting.bicep new file mode 100644 index 00000000..46781086 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/core/testing/loadtesting.bicep @@ -0,0 +1,15 @@ +param name string +param location string = resourceGroup().location +param managedIdentity bool = false +param tags object = {} + +resource loadTest 'Microsoft.LoadTestService/loadTests@2022-12-01' = { + name: name + location: location + tags: tags + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + properties: { + } +} + +output loadTestingName string = loadTest.name diff --git a/Environments/App-Base-WebApp-AKS/main.bicep b/Environments/App-Base-WebApp-AKS/main.bicep new file mode 100644 index 00000000..a382fedb --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/main.bicep @@ -0,0 +1,91 @@ +@minLength(1) +@maxLength(64) +@description('Name of the the environment which is used to generate a short unique hash used in all resources.') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string + +@description('The resource name of the AKS cluster') +param clusterName string = '' + +@description('The resource name of the Container Registry (ACR)') +param containerRegistryName string = '' + +param applicationInsightsDashboardName string = '' +param applicationInsightsName string = '' +param cosmosAccountName string = '' +param cosmosDatabaseName string = '' +param keyVaultName string = '' +param logAnalyticsName string = '' + +@description('Id of the user or app to assign application roles') +param principalId string = '' + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +// The AKS cluster to host applications +module aks './core/host/aks.bicep' = { + name: 'aks' + params: { + location: location + name: !empty(clusterName) ? clusterName : '${abbrs.containerServiceManagedClusters}${resourceToken}' + containerRegistryName: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + logAnalyticsName: monitoring.outputs.logAnalyticsWorkspaceName + keyVaultName: keyVault.outputs.name + } +} + +// The application database +module cosmos './app/db.bicep' = { + name: 'cosmos' + params: { + accountName: !empty(cosmosAccountName) ? cosmosAccountName : '${abbrs.documentDBDatabaseAccounts}${resourceToken}' + databaseName: cosmosDatabaseName + location: location + tags: tags + keyVaultName: keyVault.outputs.name + } +} + +// Store secrets in a keyvault +module keyVault './core/security/keyvault.bicep' = { + name: 'keyvault' + params: { + name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' + location: location + tags: tags + principalId: principalId + } +} + +// Monitor application with Azure Monitor +module monitoring './core/monitor/monitoring.bicep' = { + name: 'monitoring' + params: { + location: location + tags: tags + logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}' + } +} + +// Data outputs +output AZURE_COSMOS_CONNECTION_STRING_KEY string = cosmos.outputs.connectionStringKey +output AZURE_COSMOS_DATABASE_NAME string = cosmos.outputs.databaseName + +// App outputs +output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString +output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.endpoint +output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId +output AZURE_AKS_CLUSTER_NAME string = aks.outputs.clusterName +output AZURE_AKS_IDENTITY_CLIENT_ID string = aks.outputs.clusterIdentity.clientId +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = aks.outputs.containerRegistryLoginServer +output AZURE_CONTAINER_REGISTRY_NAME string = aks.outputs.containerRegistryName +output REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString diff --git a/Environments/App-Base-WebApp-AKS/main.parameters.json b/Environments/App-Base-WebApp-AKS/main.parameters.json new file mode 100644 index 00000000..67ad8524 --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/main.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "principalId": { + "value": "${AZURE_PRINCIPAL_ID}" + } + } +} \ No newline at end of file diff --git a/Environments/App-Base-WebApp-AKS/manifest.yaml b/Environments/App-Base-WebApp-AKS/manifest.yaml new file mode 100644 index 00000000..7e08cb3e --- /dev/null +++ b/Environments/App-Base-WebApp-AKS/manifest.yaml @@ -0,0 +1,28 @@ +name: App-Base-WebApp-AKS +version: 1.0.0 +summary: App Base Nodejs Mongo AKS +description: Deploys a base infra for AKS app dev with Nodejs and Mongo +runner: ARM +templatePath: azuredeploy.json + +parameters: +- id: "environmentName" + name: "Environment Name (e.g. testenv)" + description: "Name of the Environment" + type: string + required: true + +- id: "location" + name: "Region (e.g. eastus)" + description: "Location of the resources" + type: string + required: true + +- id: "principalId" + name: "principalId (e.g. )" + description: "principal id that have the permission (get list) to access key vault" + type: string + required: false + default: '' + + diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/abbreviations.json b/Environments/Contoso-Base-Shared-ACA-Dev/abbreviations.json new file mode 100644 index 00000000..289a08aa --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/abbreviations.json @@ -0,0 +1,136 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/app/api.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/app/api.bicep new file mode 100644 index 00000000..1df68145 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/app/api.bicep @@ -0,0 +1,75 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param applicationInsightsName string +param containerAppsEnvironmentName string +param containerRegistryName string +param keyVaultName string +param serviceName string = 'api' +param corsAcaUrl string +param exists bool + +resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +// Give the API access to KeyVault +module apiKeyVaultAccess '../core/security/keyvault-access.bicep' = { + name: 'api-keyvault-access' + params: { + keyVaultName: keyVaultName + principalId: apiIdentity.properties.principalId + } +} + +module app '../core/host/container-app-upsert.bicep' = { + name: '${serviceName}-container-app' + dependsOn: [ apiKeyVaultAccess ] + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identityType: 'UserAssigned' + identityName: apiIdentity.name + exists: exists + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: '1.0' + containerMemory: '2.0Gi' + env: [ + { + name: 'AZURE_CLIENT_ID' + value: apiIdentity.properties.clientId + } + { + name: 'AZURE_KEY_VAULT_ENDPOINT' + value: keyVault.properties.vaultUri + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'API_ALLOW_ORIGINS' + value: corsAcaUrl + } + ] + targetPort: 3100 + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output SERVICE_API_IDENTITY_PRINCIPAL_ID string = apiIdentity.properties.principalId +output SERVICE_API_NAME string = app.outputs.name +output SERVICE_API_URI string = app.outputs.uri +output SERVICE_API_IMAGE_NAME string = app.outputs.imageName diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/app/apim-api-policy.xml b/Environments/Contoso-Base-Shared-ACA-Dev/app/apim-api-policy.xml new file mode 100644 index 00000000..d9ac2b2d --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/app/apim-api-policy.xml @@ -0,0 +1,92 @@ + + + + + + + + {origin} + + + PUT + GET + POST + DELETE + PATCH + + +
*
+
+ +
*
+
+
+ + + + + + + Call to the @(context.Api.Name) + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failed to process the @(context.Api.Name) + + + + + + + + + + + + + + + + + + An unexpected error has occurred. + + +
diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/app/apim-api.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/app/apim-api.bicep new file mode 100644 index 00000000..b6008404 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/app/apim-api.bicep @@ -0,0 +1,120 @@ +param name string + +@description('Resource name to uniquely identify this API within the API Management service instance') +@minLength(1) +param apiName string + +@description('The Display Name of the API') +@minLength(1) +@maxLength(300) +param apiDisplayName string + +@description('Description of the API. May include HTML formatting tags.') +@minLength(1) +param apiDescription string + +@description('Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API.') +@minLength(1) +param apiPath string + +@description('Absolute URL of the web frontend') +param webFrontendUrl string + +@description('Absolute URL of the backend service implementing this API.') +param apiBackendUrl string + +@description('Resource name for backend Web App or Function App') +param apiAppName string = '' + +var apiPolicyContent = replace(loadTextContent('./apim-api-policy.xml'), '{origin}', webFrontendUrl) + +resource restApi 'Microsoft.ApiManagement/service/apis@2021-12-01-preview' = { + name: apiName + parent: apimService + properties: { + description: apiDescription + displayName: apiDisplayName + path: apiPath + protocols: [ 'https' ] + subscriptionRequired: false + type: 'http' + format: 'openapi' + serviceUrl: apiBackendUrl + value: loadTextContent('./openapi.yaml') + } +} + +resource apiPolicy 'Microsoft.ApiManagement/service/apis/policies@2021-12-01-preview' = { + name: 'policy' + parent: restApi + properties: { + format: 'rawxml' + value: apiPolicyContent + } +} + +resource apiDiagnostics 'Microsoft.ApiManagement/service/apis/diagnostics@2021-12-01-preview' = { + name: 'applicationinsights' + parent: restApi + properties: { + alwaysLog: 'allErrors' + backend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + frontend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + httpCorrelationProtocol: 'W3C' + logClientIp: true + loggerId: apimLogger.id + metrics: true + sampling: { + percentage: 100 + samplingType: 'fixed' + } + verbosity: 'verbose' + } +} + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' existing = { + name: name +} + +// Necessary due to https://github.com/Azure/bicep/issues/9594 +// placeholderName is never deployed, it is merely used to make the child name validation pass +var appNameForBicep = !empty(apiAppName) ? apiAppName : 'placeholderName' + +resource apiAppProperties 'Microsoft.Web/sites/config@2022-03-01' = if (!empty(apiAppName)) { + name: '${appNameForBicep}/web' + kind: 'string' + properties: { + apiManagementConfig: { + id: '${apimService.id}/apis/${apiName}' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' existing = { + name: 'app-insights-logger' + parent: apimService +} + +output SERVICE_API_URI string = '${apimService.properties.gatewayUrl}/${apiPath}' diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/app/db.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/app/db.bicep new file mode 100644 index 00000000..938949c6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/app/db.bicep @@ -0,0 +1,40 @@ +param accountName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [ + { + name: 'TodoList' + id: 'TodoList' + shardKey: 'Hash' + indexKey: '_id' + } + { + name: 'TodoItem' + id: 'TodoItem' + shardKey: 'Hash' + indexKey: '_id' + } +] +param databaseName string = '' +param keyVaultName string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module cosmos '../core/database/cosmos/mongo/cosmos-mongo-db.bicep' = { + name: 'cosmos-mongo' + params: { + accountName: accountName + databaseName: actualDatabaseName + location: location + collections: collections + keyVaultName: keyVaultName + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = cosmos.outputs.databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/app/openapi.yaml b/Environments/Contoso-Base-Shared-ACA-Dev/app/openapi.yaml new file mode 100644 index 00000000..c42e99ca --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/app/openapi.yaml @@ -0,0 +1,312 @@ +openapi: 3.0.0 +info: + description: Simple Todo API + version: 3.0.0 + title: Simple Todo API + contact: + email: azdevteam@microsoft.com + +components: + schemas: + TodoItem: + type: object + required: + - listId + - name + - description + description: A task that needs to be completed + properties: + id: + type: string + listId: + type: string + name: + type: string + description: + type: string + state: + $ref: "#/components/schemas/TodoState" + dueDate: + type: string + format: date-time + completedDate: + type: string + format: date-time + TodoList: + type: object + required: + - name + properties: + id: + type: string + name: + type: string + description: + type: string + description: " A list of related Todo items" + TodoState: + type: string + enum: + - todo + - inprogress + - done + parameters: + listId: + in: path + required: true + name: listId + description: The Todo list unique identifier + schema: + type: string + itemId: + in: path + required: true + name: itemId + description: The Todo item unique identifier + schema: + type: string + state: + in: path + required: true + name: state + description: The Todo item state + schema: + $ref: "#/components/schemas/TodoState" + top: + in: query + required: false + name: top + description: The max number of items to returns in a result + schema: + type: number + default: 20 + skip: + in: query + required: false + name: skip + description: The number of items to skip within the results + schema: + type: number + default: 0 + + requestBodies: + TodoList: + description: The Todo List + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: The Todo Item + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + + responses: + TodoList: + description: A Todo list result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoListArray: + description: An array of Todo lists + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: A Todo item result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + TodoItemArray: + description: An array of Todo items + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoItem" + +paths: + /lists: + get: + operationId: GetLists + summary: Gets an array of Todo lists + tags: + - Lists + parameters: + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoListArray" + post: + operationId: CreateList + summary: Creates a new Todo list + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + responses: + 201: + $ref: "#/components/responses/TodoList" + 400: + description: Invalid request schema + /lists/{listId}: + get: + operationId: GetListById + summary: Gets a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + put: + operationId: UpdateListById + summary: Updates a Todo list by unique identifier + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + 400: + description: Todo list is invalid + delete: + operationId: DeleteListById + summary: Deletes a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 204: + description: Todo list deleted successfully + 404: + description: Todo list not found + /lists/{listId}/items: + post: + operationId: CreateItem + summary: Creates a new Todo item within a list + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 201: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list not found + get: + operationId: GetItemsByListId + summary: Gets Todo items within the specified list + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list not found + /lists/{listId}/items/{itemId}: + get: + operationId: GetItemById + summary: Gets a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemById + summary: Updates a Todo item by unique identifier + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 400: + description: Todo item is invalid + 404: + description: Todo list or item not found + delete: + operationId: DeleteItemById + summary: Deletes a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 204: + description: Todo item deleted successfully + 404: + description: Todo list or item not found + /lists/{listId}/items/state/{state}: + get: + operationId: GetItemsByListIdAndState + summary: Gets a list of Todo items of a specific state + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemsStateByListId + summary: Changes the state of the specified list items + tags: + - Items + requestBody: + description: unique identifiers of the Todo items to update + content: + application/json: + schema: + type: array + items: + description: The Todo item unique identifier + type: string + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + responses: + 204: + description: Todo items updated + 400: + description: Update request is invalid diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/app/web.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/app/web.bicep new file mode 100644 index 00000000..2c692278 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/app/web.bicep @@ -0,0 +1,54 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param apiBaseUrl string +param applicationInsightsName string +param containerAppsEnvironmentName string +param containerRegistryName string +param serviceName string = 'web' +param exists bool + +resource webIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +module app '../core/host/container-app-upsert.bicep' = { + name: '${serviceName}-container-app' + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identityType: 'UserAssigned' + identityName: identityName + exists: exists + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + env: [ + { + name: 'REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'REACT_APP_API_BASE_URL' + value: apiBaseUrl + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + ] + targetPort: 80 + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +output SERVICE_WEB_IDENTITY_PRINCIPAL_ID string = webIdentity.properties.principalId +output SERVICE_WEB_NAME string = app.outputs.name +output SERVICE_WEB_URI string = app.outputs.uri +output SERVICE_WEB_IMAGE_NAME string = app.outputs.imageName diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/azuredeploy.json b/Environments/Contoso-Base-Shared-ACA-Dev/azuredeploy.json new file mode 100644 index 00000000..94f1cb0a --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/azuredeploy.json @@ -0,0 +1,1917 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "7010794115259852587" + } + }, + "parameters": { + "environmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the the environment which is used to generate a short unique hash used in all resources." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "minLength": 1, + "metadata": { + "description": "Primary location for all resources" + } + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + }, + "containerAppsEnvironmentName": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "" + } + }, + "variables": { + "$fxv#0": { + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" + }, + "abbrs": "[variables('$fxv#0')]", + "resourceToken": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "tags": { + "azd-env-name": "[parameters('environmentName')]" + }, + "apiContainerAppNameOrDefault": "[format('{0}web-{1}', variables('abbrs').appContainerApps, variables('resourceToken'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-apps", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "app" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "containerAppsEnvironmentName": "[if(not(empty(parameters('containerAppsEnvironmentName'))), createObject('value', parameters('containerAppsEnvironmentName')), createObject('value', format('{0}{1}', variables('abbrs').appManagedEnvironments, variables('resourceToken'))))]", + "logAnalyticsWorkspaceName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.logAnalyticsWorkspaceName.value]" + }, + "applicationInsightsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10266908178814002202" + }, + "description": "Creates an Azure Container Registry and an Azure Container Apps environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "containerAppsEnvironmentName": { + "type": "string" + }, + "logAnalyticsWorkspaceName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-container-apps-environment', parameters('name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "logAnalyticsWorkspaceName": { + "value": "[parameters('logAnalyticsWorkspaceName')]" + }, + "applicationInsightsName": { + "value": "[parameters('applicationInsightsName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "4766903245227392386" + }, + "description": "Creates an Azure Container Apps environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Application Insights resource" + } + }, + "daprEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Specifies if Dapr is enabled" + } + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace" + } + } + }, + "resources": [ + { + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2023-04-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "appLogsConfiguration": { + "destination": "log-analytics", + "logAnalyticsConfiguration": { + "customerId": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').customerId]", + "sharedKey": "[listKeys(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').primarySharedKey]" + } + }, + "daprAIInstrumentationKey": "[if(and(parameters('daprEnabled'), not(empty(parameters('applicationInsightsName')))), reference(resourceId('Microsoft.Insights/components', parameters('applicationInsightsName')), '2020-02-02').InstrumentationKey, '')]" + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2023-04-01-preview').defaultDomain]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.defaultDomain.value]" + }, + "environmentName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.name.value]" + }, + "environmentId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.id.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "monitoring", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "logAnalyticsName": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', parameters('logAnalyticsName')), createObject('value', format('{0}{1}', variables('abbrs').operationalInsightsWorkspaces, variables('resourceToken'))))]", + "applicationInsightsName": "[if(not(empty(parameters('applicationInsightsName'))), createObject('value', parameters('applicationInsightsName')), createObject('value', format('{0}{1}', variables('abbrs').insightsComponents, variables('resourceToken'))))]", + "applicationInsightsDashboardName": "[if(not(empty(parameters('applicationInsightsDashboardName'))), createObject('value', parameters('applicationInsightsDashboardName')), createObject('value', format('{0}{1}', variables('abbrs').portalDashboards, variables('resourceToken'))))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10041669792322197047" + }, + "description": "Creates an Application Insights instance and a Log Analytics workspace." + }, + "parameters": { + "logAnalyticsName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "loganalytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('logAnalyticsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "9622176141085970536" + }, + "description": "Creates a Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-12-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": 30, + "features": { + "searchVersion": 1 + }, + "sku": { + "name": "PerGB2018" + } + } + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "applicationinsights", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('applicationInsightsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "dashboardName": { + "value": "[parameters('applicationInsightsDashboardName')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1335628967363670282" + }, + "description": "Creates an Application Insights instance based on an existing Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "dashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "logAnalyticsWorkspaceId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]" + } + }, + { + "condition": "[not(empty(parameters('dashboardName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "application-insights-dashboard", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('dashboardName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "applicationInsightsName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "2145880658446193205" + }, + "description": "Creates a dashboard for an Application Insights instance." + }, + "parameters": { + "name": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Portal/dashboards", + "apiVersion": "2020-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "lenses": [ + { + "order": 0, + "parts": [ + { + "position": { + "x": 0, + "y": 0, + "colSpan": 2, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "id", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart", + "asset": { + "idInputName": "id", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "overview" + } + }, + { + "position": { + "x": 2, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "ProactiveDetection" + } + }, + { + "position": { + "x": 3, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + } + ], + "type": "Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:20:33.345Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 5, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-08T18:47:35.237Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "ConfigurationId", + "value": "78ce933e-e864-4b05-a27b-71fd55a6afad" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AppMapButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 0, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Usage", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 3, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:22:35.782Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + } + ], + "type": "Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Reliability", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 7, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:42:40.072Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "failures" + } + }, + { + "position": { + "x": 8, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Responsiveness\r\n", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 11, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:43:37.804Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "2a8ede4f-2bee-4b9c-aed9-2db0e8a01865", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "performance" + } + }, + { + "position": { + "x": 12, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Browser", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 15, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "MetricsExplorerJsonDefinitionId", + "value": "BrowserPerformanceTimelineMetrics" + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "createdTime": "2018-05-08T12:16:27.534Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "CurrentFilter", + "value": { + "eventTypes": [ + 4, + 1, + 3, + 5, + 2, + 6, + 13 + ], + "typeFacets": {}, + "isPermissive": false + } + }, + { + "name": "id", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "browser" + } + }, + { + "position": { + "x": 0, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "sessions/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Sessions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "users/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Users", + "color": "#7E58FF" + } + } + ], + "title": "Unique sessions and users", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "segmentationUsers" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Failed requests", + "color": "#EC008C" + } + } + ], + "title": "Failed requests", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "failures" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/duration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server response time", + "color": "#00BCF2" + } + } + ], + "title": "Server response time", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "performance" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/networkDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Page load network connect time", + "color": "#7E58FF" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/processingDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Client processing time", + "color": "#44F1C8" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/sendDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Send request time", + "color": "#EB9371" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/receiveDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Receiving response time", + "color": "#0672F1" + } + } + ], + "title": "Average page load time breakdown", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/availabilityPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability", + "color": "#47BDF5" + } + } + ], + "title": "Average availability", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "availability" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/server", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server exceptions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "dependencies/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Dependency failures", + "color": "#7E58FF" + } + } + ], + "title": "Server exceptions and Dependency failures", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processorCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Processor time", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process CPU", + "color": "#7E58FF" + } + } + ], + "title": "Average processor and process CPU utilization", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/browser", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Browser exceptions", + "color": "#47BDF5" + } + } + ], + "title": "Browser exceptions", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/count", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability test results count", + "color": "#47BDF5" + } + } + ], + "title": "Availability test results count", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processIOBytesPerSecond", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process IO rate", + "color": "#47BDF5" + } + } + ], + "title": "Average process I/O rate", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/memoryAvailableBytes", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Available memory", + "color": "#47BDF5" + } + } + ], + "title": "Average available memory", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + } + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').ConnectionString]" + }, + "instrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').InstrumentationKey]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'loganalytics')]" + ] + } + ], + "outputs": { + "applicationInsightsConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.connectionString.value]" + }, + "applicationInsightsInstrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "applicationInsightsName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.name.value]" + } + } + } + } + } + ], + "outputs": { + "API_CORS_ACA_URL": { + "type": "string", + "value": "[format('https://{0}.{1}', variables('apiContainerAppNameOrDefault'), reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.defaultDomain.value)]" + }, + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "APPLICATIONINSIGHTS_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + }, + "AZURE_CONTAINER_ENVIRONMENT_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.environmentName.value]" + }, + "AZURE_LOCATION": { + "type": "string", + "value": "[parameters('location')]" + }, + "AZURE_TENANT_ID": { + "type": "string", + "value": "[tenant().tenantId]" + }, + "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/core/host/container-apps-environment.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/core/host/container-apps-environment.bicep new file mode 100644 index 00000000..8633ba48 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/core/host/container-apps-environment.bicep @@ -0,0 +1,41 @@ +metadata description = 'Creates an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the Application Insights resource') +param applicationInsightsName string = '' + +@description('Specifies if Dapr is enabled') +param daprEnabled bool = false + +@description('Name of the Log Analytics workspace') +param logAnalyticsWorkspaceName string + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: logAnalyticsWorkspaceName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output id string = containerAppsEnvironment.id +output name string = containerAppsEnvironment.name diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/core/host/container-apps.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/core/host/container-apps.bicep new file mode 100644 index 00000000..b180d264 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/core/host/container-apps.bicep @@ -0,0 +1,23 @@ +metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param logAnalyticsWorkspaceName string +param applicationInsightsName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${name}-container-apps-environment' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + logAnalyticsWorkspaceName: logAnalyticsWorkspaceName + applicationInsightsName: applicationInsightsName + } +} + +output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output environmentName string = containerAppsEnvironment.outputs.name +output environmentId string = containerAppsEnvironment.outputs.id diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/applicationinsights-dashboard.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/applicationinsights-dashboard.bicep new file mode 100644 index 00000000..d082e668 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/applicationinsights-dashboard.bicep @@ -0,0 +1,1236 @@ +metadata description = 'Creates a dashboard for an Application Insights instance.' +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/applicationinsights.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/applicationinsights.bicep new file mode 100644 index 00000000..4b4d01e3 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/applicationinsights.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' +param name string +param dashboardName string = '' +param location string = resourceGroup().location +param tags object = {} +param logAnalyticsWorkspaceId string + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { + name: 'application-insights-dashboard' + params: { + name: dashboardName + location: location + applicationInsightsName: applicationInsights.name + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/loganalytics.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/loganalytics.bicep new file mode 100644 index 00000000..33f9dc29 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/loganalytics.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates a Log Analytics workspace.' +param name string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: name + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/monitoring.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/monitoring.bicep new file mode 100644 index 00000000..6bb05b0b --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/core/monitor/monitoring.bicep @@ -0,0 +1,32 @@ +metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string = '' +param location string = resourceGroup().location +param tags object = {} + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + name: logAnalyticsName + location: location + tags: tags + } +} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/main.bicep b/Environments/Contoso-Base-Shared-ACA-Dev/main.bicep new file mode 100644 index 00000000..8864f5ca --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/main.bicep @@ -0,0 +1,59 @@ +@minLength(1) +@maxLength(64) +@description('Name of the the environment which is used to generate a short unique hash used in all resources.') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string = resourceGroup().location + +// Optional parameters to override the default azd resource naming conventions. Update the main.parameters.json file to provide values. e.g.,: +// "resourceGroupName": { +// "value": "myGroupName" +// } +param applicationInsightsDashboardName string = '' +param applicationInsightsName string = '' +param containerAppsEnvironmentName string = '' +param logAnalyticsName string = '' + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } +var apiContainerAppNameOrDefault = '${abbrs.appContainerApps}web-${resourceToken}' +var corsAcaUrl = 'https://${apiContainerAppNameOrDefault}.${containerApps.outputs.defaultDomain}' + +// Container apps host (including container registry) +module containerApps './core/host/container-apps.bicep' = { + name: 'container-apps' + params: { + name: 'app' + location: location + tags: tags + containerAppsEnvironmentName: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' + logAnalyticsWorkspaceName: monitoring.outputs.logAnalyticsWorkspaceName + applicationInsightsName: monitoring.outputs.applicationInsightsName + } +} + +// Monitor application with Azure Monitor +module monitoring './core/monitor/monitoring.bicep' = { + name: 'monitoring' + params: { + location: location + tags: tags + logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}' + } +} + +// App outputs +output API_CORS_ACA_URL string = corsAcaUrl +output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString +output APPLICATIONINSIGHTS_NAME string = monitoring.outputs.applicationInsightsName +output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerApps.outputs.environmentName + +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId + +output REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/main.parameters.json b/Environments/Contoso-Base-Shared-ACA-Dev/main.parameters.json new file mode 100644 index 00000000..8f7787be --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/main.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-ACA-Dev/manifest.yaml b/Environments/Contoso-Base-Shared-ACA-Dev/manifest.yaml new file mode 100644 index 00000000..f95fd3b4 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Dev/manifest.yaml @@ -0,0 +1,19 @@ +name: Contoso-Base-Shared-ACA-Dev +version: 1.0.0 +summary: Contoso-Base-Shared-ACA-Dev +description: Deploys base shared env (Container environment and monitoring) for app development in ACA +runner: ARM +templatePath: azuredeploy.json + +parameters: +- id: "environmentName" + name: "Environment Name (e.g. testenv)" + description: "Name of the Environment" + type: string + required: true + +- id: "location" + name: "Region (e.g. eastus)" + description: "Location of the resources" + type: string + required: true diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/abbreviations.json b/Environments/Contoso-Base-Shared-ACA-Prod/abbreviations.json new file mode 100644 index 00000000..289a08aa --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/abbreviations.json @@ -0,0 +1,136 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/app/api.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/app/api.bicep new file mode 100644 index 00000000..1df68145 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/app/api.bicep @@ -0,0 +1,75 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param applicationInsightsName string +param containerAppsEnvironmentName string +param containerRegistryName string +param keyVaultName string +param serviceName string = 'api' +param corsAcaUrl string +param exists bool + +resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +// Give the API access to KeyVault +module apiKeyVaultAccess '../core/security/keyvault-access.bicep' = { + name: 'api-keyvault-access' + params: { + keyVaultName: keyVaultName + principalId: apiIdentity.properties.principalId + } +} + +module app '../core/host/container-app-upsert.bicep' = { + name: '${serviceName}-container-app' + dependsOn: [ apiKeyVaultAccess ] + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identityType: 'UserAssigned' + identityName: apiIdentity.name + exists: exists + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: '1.0' + containerMemory: '2.0Gi' + env: [ + { + name: 'AZURE_CLIENT_ID' + value: apiIdentity.properties.clientId + } + { + name: 'AZURE_KEY_VAULT_ENDPOINT' + value: keyVault.properties.vaultUri + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'API_ALLOW_ORIGINS' + value: corsAcaUrl + } + ] + targetPort: 3100 + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output SERVICE_API_IDENTITY_PRINCIPAL_ID string = apiIdentity.properties.principalId +output SERVICE_API_NAME string = app.outputs.name +output SERVICE_API_URI string = app.outputs.uri +output SERVICE_API_IMAGE_NAME string = app.outputs.imageName diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/app/apim-api-policy.xml b/Environments/Contoso-Base-Shared-ACA-Prod/app/apim-api-policy.xml new file mode 100644 index 00000000..d9ac2b2d --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/app/apim-api-policy.xml @@ -0,0 +1,92 @@ + + + + + + + + {origin} + + + PUT + GET + POST + DELETE + PATCH + + +
*
+
+ +
*
+
+
+ + + + + + + Call to the @(context.Api.Name) + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failed to process the @(context.Api.Name) + + + + + + + + + + + + + + + + + + An unexpected error has occurred. + + +
diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/app/apim-api.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/app/apim-api.bicep new file mode 100644 index 00000000..b6008404 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/app/apim-api.bicep @@ -0,0 +1,120 @@ +param name string + +@description('Resource name to uniquely identify this API within the API Management service instance') +@minLength(1) +param apiName string + +@description('The Display Name of the API') +@minLength(1) +@maxLength(300) +param apiDisplayName string + +@description('Description of the API. May include HTML formatting tags.') +@minLength(1) +param apiDescription string + +@description('Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API.') +@minLength(1) +param apiPath string + +@description('Absolute URL of the web frontend') +param webFrontendUrl string + +@description('Absolute URL of the backend service implementing this API.') +param apiBackendUrl string + +@description('Resource name for backend Web App or Function App') +param apiAppName string = '' + +var apiPolicyContent = replace(loadTextContent('./apim-api-policy.xml'), '{origin}', webFrontendUrl) + +resource restApi 'Microsoft.ApiManagement/service/apis@2021-12-01-preview' = { + name: apiName + parent: apimService + properties: { + description: apiDescription + displayName: apiDisplayName + path: apiPath + protocols: [ 'https' ] + subscriptionRequired: false + type: 'http' + format: 'openapi' + serviceUrl: apiBackendUrl + value: loadTextContent('./openapi.yaml') + } +} + +resource apiPolicy 'Microsoft.ApiManagement/service/apis/policies@2021-12-01-preview' = { + name: 'policy' + parent: restApi + properties: { + format: 'rawxml' + value: apiPolicyContent + } +} + +resource apiDiagnostics 'Microsoft.ApiManagement/service/apis/diagnostics@2021-12-01-preview' = { + name: 'applicationinsights' + parent: restApi + properties: { + alwaysLog: 'allErrors' + backend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + frontend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + httpCorrelationProtocol: 'W3C' + logClientIp: true + loggerId: apimLogger.id + metrics: true + sampling: { + percentage: 100 + samplingType: 'fixed' + } + verbosity: 'verbose' + } +} + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' existing = { + name: name +} + +// Necessary due to https://github.com/Azure/bicep/issues/9594 +// placeholderName is never deployed, it is merely used to make the child name validation pass +var appNameForBicep = !empty(apiAppName) ? apiAppName : 'placeholderName' + +resource apiAppProperties 'Microsoft.Web/sites/config@2022-03-01' = if (!empty(apiAppName)) { + name: '${appNameForBicep}/web' + kind: 'string' + properties: { + apiManagementConfig: { + id: '${apimService.id}/apis/${apiName}' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' existing = { + name: 'app-insights-logger' + parent: apimService +} + +output SERVICE_API_URI string = '${apimService.properties.gatewayUrl}/${apiPath}' diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/app/db.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/app/db.bicep new file mode 100644 index 00000000..938949c6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/app/db.bicep @@ -0,0 +1,40 @@ +param accountName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [ + { + name: 'TodoList' + id: 'TodoList' + shardKey: 'Hash' + indexKey: '_id' + } + { + name: 'TodoItem' + id: 'TodoItem' + shardKey: 'Hash' + indexKey: '_id' + } +] +param databaseName string = '' +param keyVaultName string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module cosmos '../core/database/cosmos/mongo/cosmos-mongo-db.bicep' = { + name: 'cosmos-mongo' + params: { + accountName: accountName + databaseName: actualDatabaseName + location: location + collections: collections + keyVaultName: keyVaultName + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = cosmos.outputs.databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/app/openapi.yaml b/Environments/Contoso-Base-Shared-ACA-Prod/app/openapi.yaml new file mode 100644 index 00000000..c42e99ca --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/app/openapi.yaml @@ -0,0 +1,312 @@ +openapi: 3.0.0 +info: + description: Simple Todo API + version: 3.0.0 + title: Simple Todo API + contact: + email: azdevteam@microsoft.com + +components: + schemas: + TodoItem: + type: object + required: + - listId + - name + - description + description: A task that needs to be completed + properties: + id: + type: string + listId: + type: string + name: + type: string + description: + type: string + state: + $ref: "#/components/schemas/TodoState" + dueDate: + type: string + format: date-time + completedDate: + type: string + format: date-time + TodoList: + type: object + required: + - name + properties: + id: + type: string + name: + type: string + description: + type: string + description: " A list of related Todo items" + TodoState: + type: string + enum: + - todo + - inprogress + - done + parameters: + listId: + in: path + required: true + name: listId + description: The Todo list unique identifier + schema: + type: string + itemId: + in: path + required: true + name: itemId + description: The Todo item unique identifier + schema: + type: string + state: + in: path + required: true + name: state + description: The Todo item state + schema: + $ref: "#/components/schemas/TodoState" + top: + in: query + required: false + name: top + description: The max number of items to returns in a result + schema: + type: number + default: 20 + skip: + in: query + required: false + name: skip + description: The number of items to skip within the results + schema: + type: number + default: 0 + + requestBodies: + TodoList: + description: The Todo List + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: The Todo Item + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + + responses: + TodoList: + description: A Todo list result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoListArray: + description: An array of Todo lists + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: A Todo item result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + TodoItemArray: + description: An array of Todo items + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoItem" + +paths: + /lists: + get: + operationId: GetLists + summary: Gets an array of Todo lists + tags: + - Lists + parameters: + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoListArray" + post: + operationId: CreateList + summary: Creates a new Todo list + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + responses: + 201: + $ref: "#/components/responses/TodoList" + 400: + description: Invalid request schema + /lists/{listId}: + get: + operationId: GetListById + summary: Gets a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + put: + operationId: UpdateListById + summary: Updates a Todo list by unique identifier + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + 400: + description: Todo list is invalid + delete: + operationId: DeleteListById + summary: Deletes a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 204: + description: Todo list deleted successfully + 404: + description: Todo list not found + /lists/{listId}/items: + post: + operationId: CreateItem + summary: Creates a new Todo item within a list + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 201: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list not found + get: + operationId: GetItemsByListId + summary: Gets Todo items within the specified list + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list not found + /lists/{listId}/items/{itemId}: + get: + operationId: GetItemById + summary: Gets a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemById + summary: Updates a Todo item by unique identifier + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 400: + description: Todo item is invalid + 404: + description: Todo list or item not found + delete: + operationId: DeleteItemById + summary: Deletes a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 204: + description: Todo item deleted successfully + 404: + description: Todo list or item not found + /lists/{listId}/items/state/{state}: + get: + operationId: GetItemsByListIdAndState + summary: Gets a list of Todo items of a specific state + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemsStateByListId + summary: Changes the state of the specified list items + tags: + - Items + requestBody: + description: unique identifiers of the Todo items to update + content: + application/json: + schema: + type: array + items: + description: The Todo item unique identifier + type: string + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + responses: + 204: + description: Todo items updated + 400: + description: Update request is invalid diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/app/web.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/app/web.bicep new file mode 100644 index 00000000..2c692278 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/app/web.bicep @@ -0,0 +1,54 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param apiBaseUrl string +param applicationInsightsName string +param containerAppsEnvironmentName string +param containerRegistryName string +param serviceName string = 'web' +param exists bool + +resource webIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +module app '../core/host/container-app-upsert.bicep' = { + name: '${serviceName}-container-app' + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identityType: 'UserAssigned' + identityName: identityName + exists: exists + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + env: [ + { + name: 'REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'REACT_APP_API_BASE_URL' + value: apiBaseUrl + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + ] + targetPort: 80 + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +output SERVICE_WEB_IDENTITY_PRINCIPAL_ID string = webIdentity.properties.principalId +output SERVICE_WEB_NAME string = app.outputs.name +output SERVICE_WEB_URI string = app.outputs.uri +output SERVICE_WEB_IMAGE_NAME string = app.outputs.imageName diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/azuredeploy.json b/Environments/Contoso-Base-Shared-ACA-Prod/azuredeploy.json new file mode 100644 index 00000000..94f1cb0a --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/azuredeploy.json @@ -0,0 +1,1917 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "7010794115259852587" + } + }, + "parameters": { + "environmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the the environment which is used to generate a short unique hash used in all resources." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "minLength": 1, + "metadata": { + "description": "Primary location for all resources" + } + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + }, + "containerAppsEnvironmentName": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "" + } + }, + "variables": { + "$fxv#0": { + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" + }, + "abbrs": "[variables('$fxv#0')]", + "resourceToken": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "tags": { + "azd-env-name": "[parameters('environmentName')]" + }, + "apiContainerAppNameOrDefault": "[format('{0}web-{1}', variables('abbrs').appContainerApps, variables('resourceToken'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-apps", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "app" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "containerAppsEnvironmentName": "[if(not(empty(parameters('containerAppsEnvironmentName'))), createObject('value', parameters('containerAppsEnvironmentName')), createObject('value', format('{0}{1}', variables('abbrs').appManagedEnvironments, variables('resourceToken'))))]", + "logAnalyticsWorkspaceName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.logAnalyticsWorkspaceName.value]" + }, + "applicationInsightsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10266908178814002202" + }, + "description": "Creates an Azure Container Registry and an Azure Container Apps environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "containerAppsEnvironmentName": { + "type": "string" + }, + "logAnalyticsWorkspaceName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-container-apps-environment', parameters('name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "logAnalyticsWorkspaceName": { + "value": "[parameters('logAnalyticsWorkspaceName')]" + }, + "applicationInsightsName": { + "value": "[parameters('applicationInsightsName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "4766903245227392386" + }, + "description": "Creates an Azure Container Apps environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Application Insights resource" + } + }, + "daprEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Specifies if Dapr is enabled" + } + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace" + } + } + }, + "resources": [ + { + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2023-04-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "appLogsConfiguration": { + "destination": "log-analytics", + "logAnalyticsConfiguration": { + "customerId": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').customerId]", + "sharedKey": "[listKeys(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').primarySharedKey]" + } + }, + "daprAIInstrumentationKey": "[if(and(parameters('daprEnabled'), not(empty(parameters('applicationInsightsName')))), reference(resourceId('Microsoft.Insights/components', parameters('applicationInsightsName')), '2020-02-02').InstrumentationKey, '')]" + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2023-04-01-preview').defaultDomain]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.defaultDomain.value]" + }, + "environmentName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.name.value]" + }, + "environmentId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.id.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "monitoring", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "logAnalyticsName": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', parameters('logAnalyticsName')), createObject('value', format('{0}{1}', variables('abbrs').operationalInsightsWorkspaces, variables('resourceToken'))))]", + "applicationInsightsName": "[if(not(empty(parameters('applicationInsightsName'))), createObject('value', parameters('applicationInsightsName')), createObject('value', format('{0}{1}', variables('abbrs').insightsComponents, variables('resourceToken'))))]", + "applicationInsightsDashboardName": "[if(not(empty(parameters('applicationInsightsDashboardName'))), createObject('value', parameters('applicationInsightsDashboardName')), createObject('value', format('{0}{1}', variables('abbrs').portalDashboards, variables('resourceToken'))))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10041669792322197047" + }, + "description": "Creates an Application Insights instance and a Log Analytics workspace." + }, + "parameters": { + "logAnalyticsName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "loganalytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('logAnalyticsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "9622176141085970536" + }, + "description": "Creates a Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-12-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": 30, + "features": { + "searchVersion": 1 + }, + "sku": { + "name": "PerGB2018" + } + } + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "applicationinsights", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('applicationInsightsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "dashboardName": { + "value": "[parameters('applicationInsightsDashboardName')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1335628967363670282" + }, + "description": "Creates an Application Insights instance based on an existing Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "dashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "logAnalyticsWorkspaceId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]" + } + }, + { + "condition": "[not(empty(parameters('dashboardName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "application-insights-dashboard", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('dashboardName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "applicationInsightsName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "2145880658446193205" + }, + "description": "Creates a dashboard for an Application Insights instance." + }, + "parameters": { + "name": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Portal/dashboards", + "apiVersion": "2020-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "lenses": [ + { + "order": 0, + "parts": [ + { + "position": { + "x": 0, + "y": 0, + "colSpan": 2, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "id", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart", + "asset": { + "idInputName": "id", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "overview" + } + }, + { + "position": { + "x": 2, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "ProactiveDetection" + } + }, + { + "position": { + "x": 3, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + } + ], + "type": "Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:20:33.345Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 5, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-08T18:47:35.237Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "ConfigurationId", + "value": "78ce933e-e864-4b05-a27b-71fd55a6afad" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AppMapButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 0, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Usage", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 3, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:22:35.782Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + } + ], + "type": "Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Reliability", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 7, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:42:40.072Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "failures" + } + }, + { + "position": { + "x": 8, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Responsiveness\r\n", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 11, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:43:37.804Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "2a8ede4f-2bee-4b9c-aed9-2db0e8a01865", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "performance" + } + }, + { + "position": { + "x": 12, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Browser", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 15, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "MetricsExplorerJsonDefinitionId", + "value": "BrowserPerformanceTimelineMetrics" + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "createdTime": "2018-05-08T12:16:27.534Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "CurrentFilter", + "value": { + "eventTypes": [ + 4, + 1, + 3, + 5, + 2, + 6, + 13 + ], + "typeFacets": {}, + "isPermissive": false + } + }, + { + "name": "id", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "browser" + } + }, + { + "position": { + "x": 0, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "sessions/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Sessions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "users/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Users", + "color": "#7E58FF" + } + } + ], + "title": "Unique sessions and users", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "segmentationUsers" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Failed requests", + "color": "#EC008C" + } + } + ], + "title": "Failed requests", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "failures" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/duration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server response time", + "color": "#00BCF2" + } + } + ], + "title": "Server response time", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "performance" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/networkDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Page load network connect time", + "color": "#7E58FF" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/processingDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Client processing time", + "color": "#44F1C8" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/sendDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Send request time", + "color": "#EB9371" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/receiveDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Receiving response time", + "color": "#0672F1" + } + } + ], + "title": "Average page load time breakdown", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/availabilityPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability", + "color": "#47BDF5" + } + } + ], + "title": "Average availability", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "availability" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/server", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server exceptions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "dependencies/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Dependency failures", + "color": "#7E58FF" + } + } + ], + "title": "Server exceptions and Dependency failures", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processorCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Processor time", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process CPU", + "color": "#7E58FF" + } + } + ], + "title": "Average processor and process CPU utilization", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/browser", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Browser exceptions", + "color": "#47BDF5" + } + } + ], + "title": "Browser exceptions", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/count", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability test results count", + "color": "#47BDF5" + } + } + ], + "title": "Availability test results count", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processIOBytesPerSecond", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process IO rate", + "color": "#47BDF5" + } + } + ], + "title": "Average process I/O rate", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/memoryAvailableBytes", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Available memory", + "color": "#47BDF5" + } + } + ], + "title": "Average available memory", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + } + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').ConnectionString]" + }, + "instrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').InstrumentationKey]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'loganalytics')]" + ] + } + ], + "outputs": { + "applicationInsightsConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.connectionString.value]" + }, + "applicationInsightsInstrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "applicationInsightsName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.name.value]" + } + } + } + } + } + ], + "outputs": { + "API_CORS_ACA_URL": { + "type": "string", + "value": "[format('https://{0}.{1}', variables('apiContainerAppNameOrDefault'), reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.defaultDomain.value)]" + }, + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "APPLICATIONINSIGHTS_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + }, + "AZURE_CONTAINER_ENVIRONMENT_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.environmentName.value]" + }, + "AZURE_LOCATION": { + "type": "string", + "value": "[parameters('location')]" + }, + "AZURE_TENANT_ID": { + "type": "string", + "value": "[tenant().tenantId]" + }, + "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/core/host/container-apps-environment.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/core/host/container-apps-environment.bicep new file mode 100644 index 00000000..8633ba48 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/core/host/container-apps-environment.bicep @@ -0,0 +1,41 @@ +metadata description = 'Creates an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the Application Insights resource') +param applicationInsightsName string = '' + +@description('Specifies if Dapr is enabled') +param daprEnabled bool = false + +@description('Name of the Log Analytics workspace') +param logAnalyticsWorkspaceName string + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: logAnalyticsWorkspaceName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output id string = containerAppsEnvironment.id +output name string = containerAppsEnvironment.name diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/core/host/container-apps.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/core/host/container-apps.bicep new file mode 100644 index 00000000..b180d264 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/core/host/container-apps.bicep @@ -0,0 +1,23 @@ +metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param logAnalyticsWorkspaceName string +param applicationInsightsName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${name}-container-apps-environment' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + logAnalyticsWorkspaceName: logAnalyticsWorkspaceName + applicationInsightsName: applicationInsightsName + } +} + +output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output environmentName string = containerAppsEnvironment.outputs.name +output environmentId string = containerAppsEnvironment.outputs.id diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/applicationinsights-dashboard.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/applicationinsights-dashboard.bicep new file mode 100644 index 00000000..d082e668 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/applicationinsights-dashboard.bicep @@ -0,0 +1,1236 @@ +metadata description = 'Creates a dashboard for an Application Insights instance.' +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/applicationinsights.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/applicationinsights.bicep new file mode 100644 index 00000000..4b4d01e3 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/applicationinsights.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' +param name string +param dashboardName string = '' +param location string = resourceGroup().location +param tags object = {} +param logAnalyticsWorkspaceId string + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { + name: 'application-insights-dashboard' + params: { + name: dashboardName + location: location + applicationInsightsName: applicationInsights.name + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/loganalytics.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/loganalytics.bicep new file mode 100644 index 00000000..33f9dc29 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/loganalytics.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates a Log Analytics workspace.' +param name string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: name + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/monitoring.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/monitoring.bicep new file mode 100644 index 00000000..6bb05b0b --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/core/monitor/monitoring.bicep @@ -0,0 +1,32 @@ +metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string = '' +param location string = resourceGroup().location +param tags object = {} + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + name: logAnalyticsName + location: location + tags: tags + } +} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/main.bicep b/Environments/Contoso-Base-Shared-ACA-Prod/main.bicep new file mode 100644 index 00000000..8864f5ca --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/main.bicep @@ -0,0 +1,59 @@ +@minLength(1) +@maxLength(64) +@description('Name of the the environment which is used to generate a short unique hash used in all resources.') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string = resourceGroup().location + +// Optional parameters to override the default azd resource naming conventions. Update the main.parameters.json file to provide values. e.g.,: +// "resourceGroupName": { +// "value": "myGroupName" +// } +param applicationInsightsDashboardName string = '' +param applicationInsightsName string = '' +param containerAppsEnvironmentName string = '' +param logAnalyticsName string = '' + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } +var apiContainerAppNameOrDefault = '${abbrs.appContainerApps}web-${resourceToken}' +var corsAcaUrl = 'https://${apiContainerAppNameOrDefault}.${containerApps.outputs.defaultDomain}' + +// Container apps host (including container registry) +module containerApps './core/host/container-apps.bicep' = { + name: 'container-apps' + params: { + name: 'app' + location: location + tags: tags + containerAppsEnvironmentName: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' + logAnalyticsWorkspaceName: monitoring.outputs.logAnalyticsWorkspaceName + applicationInsightsName: monitoring.outputs.applicationInsightsName + } +} + +// Monitor application with Azure Monitor +module monitoring './core/monitor/monitoring.bicep' = { + name: 'monitoring' + params: { + location: location + tags: tags + logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}' + } +} + +// App outputs +output API_CORS_ACA_URL string = corsAcaUrl +output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString +output APPLICATIONINSIGHTS_NAME string = monitoring.outputs.applicationInsightsName +output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerApps.outputs.environmentName + +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId + +output REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/main.parameters.json b/Environments/Contoso-Base-Shared-ACA-Prod/main.parameters.json new file mode 100644 index 00000000..8f7787be --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/main.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-ACA-Prod/manifest.yaml b/Environments/Contoso-Base-Shared-ACA-Prod/manifest.yaml new file mode 100644 index 00000000..90c5fb6d --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Prod/manifest.yaml @@ -0,0 +1,19 @@ +name: Contoso-Base-Shared-ACA-Prod +version: 1.0.0 +summary: Contoso-Base-Shared-ACA-Prod +description: Deploys base shared env (Container environment and monitoring) for app development in ACA +runner: ARM +templatePath: azuredeploy.json + +parameters: +- id: "environmentName" + name: "Environment Name (e.g. testenv)" + description: "Name of the Environment" + type: string + required: true + +- id: "location" + name: "Region (e.g. eastus)" + description: "Location of the resources" + type: string + required: true diff --git a/Environments/Contoso-Base-Shared-ACA-Test/abbreviations.json b/Environments/Contoso-Base-Shared-ACA-Test/abbreviations.json new file mode 100644 index 00000000..289a08aa --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/abbreviations.json @@ -0,0 +1,136 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-ACA-Test/app/api.bicep b/Environments/Contoso-Base-Shared-ACA-Test/app/api.bicep new file mode 100644 index 00000000..1df68145 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/app/api.bicep @@ -0,0 +1,75 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param applicationInsightsName string +param containerAppsEnvironmentName string +param containerRegistryName string +param keyVaultName string +param serviceName string = 'api' +param corsAcaUrl string +param exists bool + +resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +// Give the API access to KeyVault +module apiKeyVaultAccess '../core/security/keyvault-access.bicep' = { + name: 'api-keyvault-access' + params: { + keyVaultName: keyVaultName + principalId: apiIdentity.properties.principalId + } +} + +module app '../core/host/container-app-upsert.bicep' = { + name: '${serviceName}-container-app' + dependsOn: [ apiKeyVaultAccess ] + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identityType: 'UserAssigned' + identityName: apiIdentity.name + exists: exists + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: '1.0' + containerMemory: '2.0Gi' + env: [ + { + name: 'AZURE_CLIENT_ID' + value: apiIdentity.properties.clientId + } + { + name: 'AZURE_KEY_VAULT_ENDPOINT' + value: keyVault.properties.vaultUri + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'API_ALLOW_ORIGINS' + value: corsAcaUrl + } + ] + targetPort: 3100 + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output SERVICE_API_IDENTITY_PRINCIPAL_ID string = apiIdentity.properties.principalId +output SERVICE_API_NAME string = app.outputs.name +output SERVICE_API_URI string = app.outputs.uri +output SERVICE_API_IMAGE_NAME string = app.outputs.imageName diff --git a/Environments/Contoso-Base-Shared-ACA-Test/app/apim-api-policy.xml b/Environments/Contoso-Base-Shared-ACA-Test/app/apim-api-policy.xml new file mode 100644 index 00000000..d9ac2b2d --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/app/apim-api-policy.xml @@ -0,0 +1,92 @@ + + + + + + + + {origin} + + + PUT + GET + POST + DELETE + PATCH + + +
*
+
+ +
*
+
+
+ + + + + + + Call to the @(context.Api.Name) + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failed to process the @(context.Api.Name) + + + + + + + + + + + + + + + + + + An unexpected error has occurred. + + +
diff --git a/Environments/Contoso-Base-Shared-ACA-Test/app/apim-api.bicep b/Environments/Contoso-Base-Shared-ACA-Test/app/apim-api.bicep new file mode 100644 index 00000000..b6008404 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/app/apim-api.bicep @@ -0,0 +1,120 @@ +param name string + +@description('Resource name to uniquely identify this API within the API Management service instance') +@minLength(1) +param apiName string + +@description('The Display Name of the API') +@minLength(1) +@maxLength(300) +param apiDisplayName string + +@description('Description of the API. May include HTML formatting tags.') +@minLength(1) +param apiDescription string + +@description('Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API.') +@minLength(1) +param apiPath string + +@description('Absolute URL of the web frontend') +param webFrontendUrl string + +@description('Absolute URL of the backend service implementing this API.') +param apiBackendUrl string + +@description('Resource name for backend Web App or Function App') +param apiAppName string = '' + +var apiPolicyContent = replace(loadTextContent('./apim-api-policy.xml'), '{origin}', webFrontendUrl) + +resource restApi 'Microsoft.ApiManagement/service/apis@2021-12-01-preview' = { + name: apiName + parent: apimService + properties: { + description: apiDescription + displayName: apiDisplayName + path: apiPath + protocols: [ 'https' ] + subscriptionRequired: false + type: 'http' + format: 'openapi' + serviceUrl: apiBackendUrl + value: loadTextContent('./openapi.yaml') + } +} + +resource apiPolicy 'Microsoft.ApiManagement/service/apis/policies@2021-12-01-preview' = { + name: 'policy' + parent: restApi + properties: { + format: 'rawxml' + value: apiPolicyContent + } +} + +resource apiDiagnostics 'Microsoft.ApiManagement/service/apis/diagnostics@2021-12-01-preview' = { + name: 'applicationinsights' + parent: restApi + properties: { + alwaysLog: 'allErrors' + backend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + frontend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + httpCorrelationProtocol: 'W3C' + logClientIp: true + loggerId: apimLogger.id + metrics: true + sampling: { + percentage: 100 + samplingType: 'fixed' + } + verbosity: 'verbose' + } +} + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' existing = { + name: name +} + +// Necessary due to https://github.com/Azure/bicep/issues/9594 +// placeholderName is never deployed, it is merely used to make the child name validation pass +var appNameForBicep = !empty(apiAppName) ? apiAppName : 'placeholderName' + +resource apiAppProperties 'Microsoft.Web/sites/config@2022-03-01' = if (!empty(apiAppName)) { + name: '${appNameForBicep}/web' + kind: 'string' + properties: { + apiManagementConfig: { + id: '${apimService.id}/apis/${apiName}' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' existing = { + name: 'app-insights-logger' + parent: apimService +} + +output SERVICE_API_URI string = '${apimService.properties.gatewayUrl}/${apiPath}' diff --git a/Environments/Contoso-Base-Shared-ACA-Test/app/db.bicep b/Environments/Contoso-Base-Shared-ACA-Test/app/db.bicep new file mode 100644 index 00000000..938949c6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/app/db.bicep @@ -0,0 +1,40 @@ +param accountName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [ + { + name: 'TodoList' + id: 'TodoList' + shardKey: 'Hash' + indexKey: '_id' + } + { + name: 'TodoItem' + id: 'TodoItem' + shardKey: 'Hash' + indexKey: '_id' + } +] +param databaseName string = '' +param keyVaultName string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module cosmos '../core/database/cosmos/mongo/cosmos-mongo-db.bicep' = { + name: 'cosmos-mongo' + params: { + accountName: accountName + databaseName: actualDatabaseName + location: location + collections: collections + keyVaultName: keyVaultName + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = cosmos.outputs.databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/Contoso-Base-Shared-ACA-Test/app/openapi.yaml b/Environments/Contoso-Base-Shared-ACA-Test/app/openapi.yaml new file mode 100644 index 00000000..c42e99ca --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/app/openapi.yaml @@ -0,0 +1,312 @@ +openapi: 3.0.0 +info: + description: Simple Todo API + version: 3.0.0 + title: Simple Todo API + contact: + email: azdevteam@microsoft.com + +components: + schemas: + TodoItem: + type: object + required: + - listId + - name + - description + description: A task that needs to be completed + properties: + id: + type: string + listId: + type: string + name: + type: string + description: + type: string + state: + $ref: "#/components/schemas/TodoState" + dueDate: + type: string + format: date-time + completedDate: + type: string + format: date-time + TodoList: + type: object + required: + - name + properties: + id: + type: string + name: + type: string + description: + type: string + description: " A list of related Todo items" + TodoState: + type: string + enum: + - todo + - inprogress + - done + parameters: + listId: + in: path + required: true + name: listId + description: The Todo list unique identifier + schema: + type: string + itemId: + in: path + required: true + name: itemId + description: The Todo item unique identifier + schema: + type: string + state: + in: path + required: true + name: state + description: The Todo item state + schema: + $ref: "#/components/schemas/TodoState" + top: + in: query + required: false + name: top + description: The max number of items to returns in a result + schema: + type: number + default: 20 + skip: + in: query + required: false + name: skip + description: The number of items to skip within the results + schema: + type: number + default: 0 + + requestBodies: + TodoList: + description: The Todo List + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: The Todo Item + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + + responses: + TodoList: + description: A Todo list result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoListArray: + description: An array of Todo lists + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: A Todo item result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + TodoItemArray: + description: An array of Todo items + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoItem" + +paths: + /lists: + get: + operationId: GetLists + summary: Gets an array of Todo lists + tags: + - Lists + parameters: + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoListArray" + post: + operationId: CreateList + summary: Creates a new Todo list + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + responses: + 201: + $ref: "#/components/responses/TodoList" + 400: + description: Invalid request schema + /lists/{listId}: + get: + operationId: GetListById + summary: Gets a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + put: + operationId: UpdateListById + summary: Updates a Todo list by unique identifier + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + 400: + description: Todo list is invalid + delete: + operationId: DeleteListById + summary: Deletes a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 204: + description: Todo list deleted successfully + 404: + description: Todo list not found + /lists/{listId}/items: + post: + operationId: CreateItem + summary: Creates a new Todo item within a list + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 201: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list not found + get: + operationId: GetItemsByListId + summary: Gets Todo items within the specified list + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list not found + /lists/{listId}/items/{itemId}: + get: + operationId: GetItemById + summary: Gets a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemById + summary: Updates a Todo item by unique identifier + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 400: + description: Todo item is invalid + 404: + description: Todo list or item not found + delete: + operationId: DeleteItemById + summary: Deletes a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 204: + description: Todo item deleted successfully + 404: + description: Todo list or item not found + /lists/{listId}/items/state/{state}: + get: + operationId: GetItemsByListIdAndState + summary: Gets a list of Todo items of a specific state + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemsStateByListId + summary: Changes the state of the specified list items + tags: + - Items + requestBody: + description: unique identifiers of the Todo items to update + content: + application/json: + schema: + type: array + items: + description: The Todo item unique identifier + type: string + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + responses: + 204: + description: Todo items updated + 400: + description: Update request is invalid diff --git a/Environments/Contoso-Base-Shared-ACA-Test/app/web.bicep b/Environments/Contoso-Base-Shared-ACA-Test/app/web.bicep new file mode 100644 index 00000000..2c692278 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/app/web.bicep @@ -0,0 +1,54 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param apiBaseUrl string +param applicationInsightsName string +param containerAppsEnvironmentName string +param containerRegistryName string +param serviceName string = 'web' +param exists bool + +resource webIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + +module app '../core/host/container-app-upsert.bicep' = { + name: '${serviceName}-container-app' + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identityType: 'UserAssigned' + identityName: identityName + exists: exists + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + env: [ + { + name: 'REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + { + name: 'REACT_APP_API_BASE_URL' + value: apiBaseUrl + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.properties.ConnectionString + } + ] + targetPort: 80 + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} + +output SERVICE_WEB_IDENTITY_PRINCIPAL_ID string = webIdentity.properties.principalId +output SERVICE_WEB_NAME string = app.outputs.name +output SERVICE_WEB_URI string = app.outputs.uri +output SERVICE_WEB_IMAGE_NAME string = app.outputs.imageName diff --git a/Environments/Contoso-Base-Shared-ACA-Test/azuredeploy.json b/Environments/Contoso-Base-Shared-ACA-Test/azuredeploy.json new file mode 100644 index 00000000..94f1cb0a --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/azuredeploy.json @@ -0,0 +1,1917 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "7010794115259852587" + } + }, + "parameters": { + "environmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the the environment which is used to generate a short unique hash used in all resources." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "minLength": 1, + "metadata": { + "description": "Primary location for all resources" + } + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + }, + "containerAppsEnvironmentName": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "" + } + }, + "variables": { + "$fxv#0": { + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" + }, + "abbrs": "[variables('$fxv#0')]", + "resourceToken": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "tags": { + "azd-env-name": "[parameters('environmentName')]" + }, + "apiContainerAppNameOrDefault": "[format('{0}web-{1}', variables('abbrs').appContainerApps, variables('resourceToken'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-apps", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "app" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "containerAppsEnvironmentName": "[if(not(empty(parameters('containerAppsEnvironmentName'))), createObject('value', parameters('containerAppsEnvironmentName')), createObject('value', format('{0}{1}', variables('abbrs').appManagedEnvironments, variables('resourceToken'))))]", + "logAnalyticsWorkspaceName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.logAnalyticsWorkspaceName.value]" + }, + "applicationInsightsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10266908178814002202" + }, + "description": "Creates an Azure Container Registry and an Azure Container Apps environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "containerAppsEnvironmentName": { + "type": "string" + }, + "logAnalyticsWorkspaceName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-container-apps-environment', parameters('name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerAppsEnvironmentName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "logAnalyticsWorkspaceName": { + "value": "[parameters('logAnalyticsWorkspaceName')]" + }, + "applicationInsightsName": { + "value": "[parameters('applicationInsightsName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "4766903245227392386" + }, + "description": "Creates an Azure Container Apps environment." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Application Insights resource" + } + }, + "daprEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Specifies if Dapr is enabled" + } + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace" + } + } + }, + "resources": [ + { + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2023-04-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "appLogsConfiguration": { + "destination": "log-analytics", + "logAnalyticsConfiguration": { + "customerId": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').customerId]", + "sharedKey": "[listKeys(resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').primarySharedKey]" + } + }, + "daprAIInstrumentationKey": "[if(and(parameters('daprEnabled'), not(empty(parameters('applicationInsightsName')))), reference(resourceId('Microsoft.Insights/components', parameters('applicationInsightsName')), '2020-02-02').InstrumentationKey, '')]" + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2023-04-01-preview').defaultDomain]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + } + ], + "outputs": { + "defaultDomain": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.defaultDomain.value]" + }, + "environmentName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.name.value]" + }, + "environmentId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-container-apps-environment', parameters('name'))), '2022-09-01').outputs.id.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "monitoring", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "logAnalyticsName": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', parameters('logAnalyticsName')), createObject('value', format('{0}{1}', variables('abbrs').operationalInsightsWorkspaces, variables('resourceToken'))))]", + "applicationInsightsName": "[if(not(empty(parameters('applicationInsightsName'))), createObject('value', parameters('applicationInsightsName')), createObject('value', format('{0}{1}', variables('abbrs').insightsComponents, variables('resourceToken'))))]", + "applicationInsightsDashboardName": "[if(not(empty(parameters('applicationInsightsDashboardName'))), createObject('value', parameters('applicationInsightsDashboardName')), createObject('value', format('{0}{1}', variables('abbrs').portalDashboards, variables('resourceToken'))))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10041669792322197047" + }, + "description": "Creates an Application Insights instance and a Log Analytics workspace." + }, + "parameters": { + "logAnalyticsName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "loganalytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('logAnalyticsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "9622176141085970536" + }, + "description": "Creates a Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-12-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": 30, + "features": { + "searchVersion": 1 + }, + "sku": { + "name": "PerGB2018" + } + } + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "applicationinsights", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('applicationInsightsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "dashboardName": { + "value": "[parameters('applicationInsightsDashboardName')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1335628967363670282" + }, + "description": "Creates an Application Insights instance based on an existing Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "dashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "logAnalyticsWorkspaceId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]" + } + }, + { + "condition": "[not(empty(parameters('dashboardName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "application-insights-dashboard", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('dashboardName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "applicationInsightsName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "2145880658446193205" + }, + "description": "Creates a dashboard for an Application Insights instance." + }, + "parameters": { + "name": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Portal/dashboards", + "apiVersion": "2020-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "lenses": [ + { + "order": 0, + "parts": [ + { + "position": { + "x": 0, + "y": 0, + "colSpan": 2, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "id", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart", + "asset": { + "idInputName": "id", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "overview" + } + }, + { + "position": { + "x": 2, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "ProactiveDetection" + } + }, + { + "position": { + "x": 3, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + } + ], + "type": "Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:20:33.345Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 5, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-08T18:47:35.237Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "ConfigurationId", + "value": "78ce933e-e864-4b05-a27b-71fd55a6afad" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AppMapButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 0, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Usage", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 3, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:22:35.782Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + } + ], + "type": "Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Reliability", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 7, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:42:40.072Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "failures" + } + }, + { + "position": { + "x": 8, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Responsiveness\r\n", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 11, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:43:37.804Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "2a8ede4f-2bee-4b9c-aed9-2db0e8a01865", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "performance" + } + }, + { + "position": { + "x": 12, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Browser", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 15, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "MetricsExplorerJsonDefinitionId", + "value": "BrowserPerformanceTimelineMetrics" + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "createdTime": "2018-05-08T12:16:27.534Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "CurrentFilter", + "value": { + "eventTypes": [ + 4, + 1, + 3, + 5, + 2, + 6, + 13 + ], + "typeFacets": {}, + "isPermissive": false + } + }, + { + "name": "id", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "browser" + } + }, + { + "position": { + "x": 0, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "sessions/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Sessions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "users/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Users", + "color": "#7E58FF" + } + } + ], + "title": "Unique sessions and users", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "segmentationUsers" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Failed requests", + "color": "#EC008C" + } + } + ], + "title": "Failed requests", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "failures" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/duration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server response time", + "color": "#00BCF2" + } + } + ], + "title": "Server response time", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "performance" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/networkDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Page load network connect time", + "color": "#7E58FF" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/processingDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Client processing time", + "color": "#44F1C8" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/sendDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Send request time", + "color": "#EB9371" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/receiveDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Receiving response time", + "color": "#0672F1" + } + } + ], + "title": "Average page load time breakdown", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/availabilityPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability", + "color": "#47BDF5" + } + } + ], + "title": "Average availability", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "availability" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/server", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server exceptions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "dependencies/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Dependency failures", + "color": "#7E58FF" + } + } + ], + "title": "Server exceptions and Dependency failures", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processorCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Processor time", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process CPU", + "color": "#7E58FF" + } + } + ], + "title": "Average processor and process CPU utilization", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/browser", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Browser exceptions", + "color": "#47BDF5" + } + } + ], + "title": "Browser exceptions", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/count", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability test results count", + "color": "#47BDF5" + } + } + ], + "title": "Availability test results count", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processIOBytesPerSecond", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process IO rate", + "color": "#47BDF5" + } + } + ], + "title": "Average process I/O rate", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/memoryAvailableBytes", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Available memory", + "color": "#47BDF5" + } + } + ], + "title": "Average available memory", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + } + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').ConnectionString]" + }, + "instrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').InstrumentationKey]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'loganalytics')]" + ] + } + ], + "outputs": { + "applicationInsightsConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.connectionString.value]" + }, + "applicationInsightsInstrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "applicationInsightsName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.name.value]" + } + } + } + } + } + ], + "outputs": { + "API_CORS_ACA_URL": { + "type": "string", + "value": "[format('https://{0}.{1}', variables('apiContainerAppNameOrDefault'), reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.defaultDomain.value)]" + }, + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "APPLICATIONINSIGHTS_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsName.value]" + }, + "AZURE_CONTAINER_ENVIRONMENT_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-apps'), '2022-09-01').outputs.environmentName.value]" + }, + "AZURE_LOCATION": { + "type": "string", + "value": "[parameters('location')]" + }, + "AZURE_TENANT_ID": { + "type": "string", + "value": "[tenant().tenantId]" + }, + "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-ACA-Test/core/host/container-apps-environment.bicep b/Environments/Contoso-Base-Shared-ACA-Test/core/host/container-apps-environment.bicep new file mode 100644 index 00000000..8633ba48 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/core/host/container-apps-environment.bicep @@ -0,0 +1,41 @@ +metadata description = 'Creates an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the Application Insights resource') +param applicationInsightsName string = '' + +@description('Specifies if Dapr is enabled') +param daprEnabled bool = false + +@description('Name of the Log Analytics workspace') +param logAnalyticsWorkspaceName string + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: logAnalyticsWorkspaceName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output id string = containerAppsEnvironment.id +output name string = containerAppsEnvironment.name diff --git a/Environments/Contoso-Base-Shared-ACA-Test/core/host/container-apps.bicep b/Environments/Contoso-Base-Shared-ACA-Test/core/host/container-apps.bicep new file mode 100644 index 00000000..b180d264 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/core/host/container-apps.bicep @@ -0,0 +1,23 @@ +metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param logAnalyticsWorkspaceName string +param applicationInsightsName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${name}-container-apps-environment' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + logAnalyticsWorkspaceName: logAnalyticsWorkspaceName + applicationInsightsName: applicationInsightsName + } +} + +output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output environmentName string = containerAppsEnvironment.outputs.name +output environmentId string = containerAppsEnvironment.outputs.id diff --git a/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/applicationinsights-dashboard.bicep b/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/applicationinsights-dashboard.bicep new file mode 100644 index 00000000..d082e668 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/applicationinsights-dashboard.bicep @@ -0,0 +1,1236 @@ +metadata description = 'Creates a dashboard for an Application Insights instance.' +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} diff --git a/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/applicationinsights.bicep b/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/applicationinsights.bicep new file mode 100644 index 00000000..4b4d01e3 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/applicationinsights.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' +param name string +param dashboardName string = '' +param location string = resourceGroup().location +param tags object = {} +param logAnalyticsWorkspaceId string + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { + name: 'application-insights-dashboard' + params: { + name: dashboardName + location: location + applicationInsightsName: applicationInsights.name + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/loganalytics.bicep b/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/loganalytics.bicep new file mode 100644 index 00000000..33f9dc29 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/loganalytics.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates a Log Analytics workspace.' +param name string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: name + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/monitoring.bicep b/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/monitoring.bicep new file mode 100644 index 00000000..6bb05b0b --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/core/monitor/monitoring.bicep @@ -0,0 +1,32 @@ +metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string = '' +param location string = resourceGroup().location +param tags object = {} + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + name: logAnalyticsName + location: location + tags: tags + } +} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/Environments/Contoso-Base-Shared-ACA-Test/main.bicep b/Environments/Contoso-Base-Shared-ACA-Test/main.bicep new file mode 100644 index 00000000..8864f5ca --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/main.bicep @@ -0,0 +1,59 @@ +@minLength(1) +@maxLength(64) +@description('Name of the the environment which is used to generate a short unique hash used in all resources.') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string = resourceGroup().location + +// Optional parameters to override the default azd resource naming conventions. Update the main.parameters.json file to provide values. e.g.,: +// "resourceGroupName": { +// "value": "myGroupName" +// } +param applicationInsightsDashboardName string = '' +param applicationInsightsName string = '' +param containerAppsEnvironmentName string = '' +param logAnalyticsName string = '' + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } +var apiContainerAppNameOrDefault = '${abbrs.appContainerApps}web-${resourceToken}' +var corsAcaUrl = 'https://${apiContainerAppNameOrDefault}.${containerApps.outputs.defaultDomain}' + +// Container apps host (including container registry) +module containerApps './core/host/container-apps.bicep' = { + name: 'container-apps' + params: { + name: 'app' + location: location + tags: tags + containerAppsEnvironmentName: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' + logAnalyticsWorkspaceName: monitoring.outputs.logAnalyticsWorkspaceName + applicationInsightsName: monitoring.outputs.applicationInsightsName + } +} + +// Monitor application with Azure Monitor +module monitoring './core/monitor/monitoring.bicep' = { + name: 'monitoring' + params: { + location: location + tags: tags + logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}' + } +} + +// App outputs +output API_CORS_ACA_URL string = corsAcaUrl +output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString +output APPLICATIONINSIGHTS_NAME string = monitoring.outputs.applicationInsightsName +output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerApps.outputs.environmentName + +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId + +output REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString diff --git a/Environments/Contoso-Base-Shared-ACA-Test/main.parameters.json b/Environments/Contoso-Base-Shared-ACA-Test/main.parameters.json new file mode 100644 index 00000000..8f7787be --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/main.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-ACA-Test/manifest.yaml b/Environments/Contoso-Base-Shared-ACA-Test/manifest.yaml new file mode 100644 index 00000000..e0e54513 --- /dev/null +++ b/Environments/Contoso-Base-Shared-ACA-Test/manifest.yaml @@ -0,0 +1,19 @@ +name: Contoso-Base-Shared-ACA-Test +version: 1.0.0 +summary: Contoso-Base-Shared-ACA-Test +description: Deploys base shared env (Container environment and monitoring) for app development in ACA +runner: ARM +templatePath: azuredeploy.json + +parameters: +- id: "environmentName" + name: "Environment Name (e.g. testenv)" + description: "Name of the Environment" + type: string + required: true + +- id: "location" + name: "Region (e.g. eastus)" + description: "Location of the resources" + type: string + required: true diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/abbreviations.json b/Environments/Contoso-Base-Shared-AKS-Dev/abbreviations.json new file mode 100644 index 00000000..289a08aa --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/abbreviations.json @@ -0,0 +1,136 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/app/db.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/app/db.bicep new file mode 100644 index 00000000..938949c6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/app/db.bicep @@ -0,0 +1,40 @@ +param accountName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [ + { + name: 'TodoList' + id: 'TodoList' + shardKey: 'Hash' + indexKey: '_id' + } + { + name: 'TodoItem' + id: 'TodoItem' + shardKey: 'Hash' + indexKey: '_id' + } +] +param databaseName string = '' +param keyVaultName string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module cosmos '../core/database/cosmos/mongo/cosmos-mongo-db.bicep' = { + name: 'cosmos-mongo' + params: { + accountName: accountName + databaseName: actualDatabaseName + location: location + collections: collections + keyVaultName: keyVaultName + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = cosmos.outputs.databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/azuredeploy.json b/Environments/Contoso-Base-Shared-AKS-Dev/azuredeploy.json new file mode 100644 index 00000000..0b774988 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/azuredeploy.json @@ -0,0 +1,2757 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "11426766062419716265" + } + }, + "parameters": { + "environmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the the environment which is used to generate a short unique hash used in all resources." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "minLength": 1, + "metadata": { + "description": "Primary location for all resources" + } + }, + "clusterName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource name of the AKS cluster" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource name of the Container Registry (ACR)" + } + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "" + } + }, + "variables": { + "$fxv#0": { + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" + }, + "abbrs": "[variables('$fxv#0')]", + "resourceToken": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "tags": { + "azd-env-name": "[parameters('environmentName')]" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aks", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "name": "[if(not(empty(parameters('clusterName'))), createObject('value', parameters('clusterName')), createObject('value', format('{0}{1}', variables('abbrs').containerServiceManagedClusters, variables('resourceToken'))))]", + "containerRegistryName": "[if(not(empty(parameters('containerRegistryName'))), createObject('value', parameters('containerRegistryName')), createObject('value', format('{0}{1}', variables('abbrs').containerRegistryRegistries, variables('resourceToken'))))]", + "logAnalyticsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.logAnalyticsWorkspaceName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "13278918148041449332" + }, + "description": "Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool as well as an additional user agent pool." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name for the AKS managed cluster" + } + }, + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The name for the Azure container registry (ACR)" + } + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the connected log analytics workspace" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The Azure region/location for the AKS resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom tags to apply to the AKS resources" + } + }, + "addOns": { + "type": "object", + "defaultValue": { + "azurePolicy": { + "enabled": true, + "config": { + "version": "v2" + } + }, + "keyVault": { + "enabled": true, + "config": { + "enableSecretRotation": "true", + "rotationPollInterval": "2m" + } + }, + "openServiceMesh": { + "enabled": false, + "config": {} + }, + "omsAgent": { + "enabled": true, + "config": {} + }, + "applicationGateway": { + "enabled": false, + "config": {} + } + }, + "metadata": { + "description": "AKS add-ons configuration" + } + }, + "sku": { + "type": "string", + "defaultValue": "Free", + "allowedValues": [ + "Free", + "Paid", + "Standard" + ], + "metadata": { + "description": "The managed cluster SKU." + } + }, + "loadBalancerSku": { + "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "basic", + "standard" + ], + "metadata": { + "description": "The load balancer SKU to use for ingress into the AKS cluster" + } + }, + "networkPlugin": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "kubenet", + "none" + ], + "metadata": { + "description": "Network plugin used for building the Kubernetes network." + } + }, + "networkPolicy": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "calico" + ], + "metadata": { + "description": "Network policy used for building the Kubernetes network." + } + }, + "dnsPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The DNS prefix to associate with the AKS cluster" + } + }, + "nodeResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the resource group for the managed resources of the AKS cluster" + } + }, + "systemPoolType": { + "type": "string", + "defaultValue": "CostOptimised", + "allowedValues": [ + "CostOptimised", + "Standard", + "HighSpec", + "Custom" + ], + "metadata": { + "description": "The System Pool Preset sizing" + } + }, + "agentPoolType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "CostOptimised", + "Standard", + "HighSpec", + "Custom" + ], + "metadata": { + "description": "The User Pool Preset sizing" + } + }, + "systemPoolConfig": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom configuration of system node pool" + } + }, + "agentPoolConfig": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom configuration of user node pool" + } + }, + "principalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Id of the user or app to assign application roles" + } + }, + "kubernetesVersion": { + "type": "string", + "defaultValue": "1.27.7", + "metadata": { + "description": "Kubernetes Version" + } + }, + "aadTenantId": { + "type": "string", + "defaultValue": "[tenant().tenantId]", + "metadata": { + "description": "The Tenant ID associated to the Azure Active Directory" + } + }, + "enableRbac": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether RBAC is enabled for local accounts" + } + }, + "disableLocalAccounts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "If set to true, getting static credentials will be disabled for this cluster." + } + }, + "enableAzureRbac": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable RBAC using AAD" + } + }, + "webAppRoutingAddon": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether web app routing (preview) add-on is enabled" + } + } + }, + "variables": { + "omsAgentConfig": "[if(and(and(not(empty(parameters('logAnalyticsName'))), not(empty(parameters('addOns').omsAgent))), parameters('addOns').omsAgent.enabled), union(parameters('addOns').omsAgent, createObject('config', createObject('logAnalyticsWorkspaceResourceID', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))))), createObject())]", + "addOnsConfig": "[union(if(and(not(empty(parameters('addOns').azurePolicy)), parameters('addOns').azurePolicy.enabled), createObject('azurepolicy', parameters('addOns').azurePolicy), createObject()), if(and(not(empty(parameters('addOns').keyVault)), parameters('addOns').keyVault.enabled), createObject('azureKeyvaultSecretsProvider', parameters('addOns').keyVault), createObject()), if(and(not(empty(parameters('addOns').openServiceMesh)), parameters('addOns').openServiceMesh.enabled), createObject('openServiceMesh', parameters('addOns').openServiceMesh), createObject()), if(and(not(empty(parameters('addOns').omsAgent)), parameters('addOns').omsAgent.enabled), createObject('omsagent', variables('omsAgentConfig')), createObject()), if(and(not(empty(parameters('addOns').applicationGateway)), parameters('addOns').applicationGateway.enabled), createObject('ingressApplicationGateway', parameters('addOns').applicationGateway), createObject()))]", + "systemPoolSpec": "[if(not(empty(parameters('systemPoolConfig'))), parameters('systemPoolConfig'), variables('nodePoolPresets')[parameters('systemPoolType')])]", + "hasAgentPool": "[or(not(empty(parameters('agentPoolConfig'))), not(empty(parameters('agentPoolType'))))]", + "agentPoolSpec": "[if(and(variables('hasAgentPool'), not(empty(parameters('agentPoolConfig')))), parameters('agentPoolConfig'), if(empty(parameters('agentPoolType')), createObject(), variables('nodePoolPresets')[parameters('agentPoolType')]))]", + "nodePoolBase": { + "osType": "Linux", + "maxPods": 30, + "type": "VirtualMachineScaleSets", + "upgradeSettings": { + "maxSurge": "33%" + } + }, + "nodePoolPresets": { + "CostOptimised": { + "vmSize": "Standard_B4ms", + "count": 1, + "minCount": 1, + "maxCount": 3, + "enableAutoScaling": true, + "availabilityZones": [] + }, + "Standard": { + "vmSize": "Standard_DS2_v2", + "count": 3, + "minCount": 3, + "maxCount": 5, + "enableAutoScaling": true, + "availabilityZones": [ + "1", + "2", + "3" + ] + }, + "HighSpec": { + "vmSize": "Standard_D4s_v3", + "count": 3, + "minCount": 3, + "maxCount": 5, + "enableAutoScaling": true, + "availabilityZones": [ + "1", + "2", + "3" + ] + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "managed-cluster", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "systemPoolConfig": { + "value": "[union(createObject('name', 'npsystem', 'mode', 'System'), variables('nodePoolBase'), variables('systemPoolSpec'))]" + }, + "nodeResourceGroupName": { + "value": "[parameters('nodeResourceGroupName')]" + }, + "sku": { + "value": "[parameters('sku')]" + }, + "dnsPrefix": { + "value": "[parameters('dnsPrefix')]" + }, + "kubernetesVersion": { + "value": "[parameters('kubernetesVersion')]" + }, + "addOns": { + "value": "[variables('addOnsConfig')]" + }, + "workspaceId": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))), createObject('value', ''))]", + "enableAad": { + "value": "[and(parameters('enableAzureRbac'), not(equals(parameters('aadTenantId'), '')))]" + }, + "disableLocalAccounts": { + "value": "[parameters('disableLocalAccounts')]" + }, + "aadTenantId": { + "value": "[parameters('aadTenantId')]" + }, + "enableRbac": { + "value": "[parameters('enableRbac')]" + }, + "enableAzureRbac": { + "value": "[parameters('enableAzureRbac')]" + }, + "webAppRoutingAddon": { + "value": "[parameters('webAppRoutingAddon')]" + }, + "loadBalancerSku": { + "value": "[parameters('loadBalancerSku')]" + }, + "networkPlugin": { + "value": "[parameters('networkPlugin')]" + }, + "networkPolicy": { + "value": "[parameters('networkPolicy')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8184151159222198677" + }, + "description": "Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name for the AKS managed cluster" + } + }, + "nodeResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the resource group for the managed resources of the AKS cluster" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The Azure region/location for the AKS resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom tags to apply to the AKS resources" + } + }, + "kubernetesVersion": { + "type": "string", + "defaultValue": "1.27.7", + "metadata": { + "description": "Kubernetes Version" + } + }, + "enableRbac": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether RBAC is enabled for local accounts" + } + }, + "webAppRoutingAddon": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether web app routing (preview) add-on is enabled" + } + }, + "enableAad": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable Azure Active Directory integration" + } + }, + "enableAzureRbac": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable RBAC using AAD" + } + }, + "aadTenantId": { + "type": "string", + "defaultValue": "[tenant().tenantId]", + "metadata": { + "description": "The Tenant ID associated to the Azure Active Directory" + } + }, + "loadBalancerSku": { + "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "basic", + "standard" + ], + "metadata": { + "description": "The load balancer SKU to use for ingress into the AKS cluster" + } + }, + "networkPlugin": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "kubenet", + "none" + ], + "metadata": { + "description": "Network plugin used for building the Kubernetes network." + } + }, + "networkPolicy": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "calico" + ], + "metadata": { + "description": "Network policy used for building the Kubernetes network." + } + }, + "disableLocalAccounts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "If set to true, getting static credentials will be disabled for this cluster." + } + }, + "sku": { + "type": "string", + "defaultValue": "Free", + "allowedValues": [ + "Free", + "Paid", + "Standard" + ], + "metadata": { + "description": "The managed cluster SKU." + } + }, + "addOns": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Configuration of AKS add-ons" + } + }, + "workspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The log analytics workspace id used for logging & monitoring" + } + }, + "systemPoolConfig": { + "type": "object", + "metadata": { + "description": "The node pool configuration for the System agent pool" + } + }, + "dnsPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The DNS prefix to associate with the AKS cluster" + } + } + }, + "variables": { + "aksDiagCategories": [ + "cluster-autoscaler", + "kube-controller-manager", + "kube-audit-admin", + "guard" + ] + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters", + "apiVersion": "2023-10-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "sku": { + "name": "Base", + "tier": "[parameters('sku')]" + }, + "properties": { + "nodeResourceGroup": "[if(not(empty(parameters('nodeResourceGroupName'))), parameters('nodeResourceGroupName'), format('rg-mc-{0}', parameters('name')))]", + "kubernetesVersion": "[parameters('kubernetesVersion')]", + "dnsPrefix": "[if(empty(parameters('dnsPrefix')), format('{0}-dns', parameters('name')), parameters('dnsPrefix'))]", + "enableRBAC": "[parameters('enableRbac')]", + "aadProfile": "[if(parameters('enableAad'), createObject('managed', true(), 'enableAzureRBAC', parameters('enableAzureRbac'), 'tenantID', parameters('aadTenantId')), null())]", + "agentPoolProfiles": [ + "[parameters('systemPoolConfig')]" + ], + "networkProfile": { + "loadBalancerSku": "[parameters('loadBalancerSku')]", + "networkPlugin": "[parameters('networkPlugin')]", + "networkPolicy": "[parameters('networkPolicy')]" + }, + "disableLocalAccounts": "[and(parameters('disableLocalAccounts'), parameters('enableAad'))]", + "addonProfiles": "[parameters('addOns')]", + "ingressProfile": { + "webAppRouting": { + "enabled": "[parameters('webAppRoutingAddon')]" + } + } + } + }, + { + "condition": "[not(empty(parameters('workspaceId')))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerService/managedClusters/{0}', parameters('name'))]", + "name": "aks-diagnostics", + "properties": { + "copy": [ + { + "name": "logs", + "count": "[length(variables('aksDiagCategories'))]", + "input": { + "category": "[variables('aksDiagCategories')[copyIndex('logs')]]", + "enabled": true + } + } + ], + "workspaceId": "[parameters('workspaceId')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerService/managedClusters', parameters('name'))]" + ] + } + ], + "outputs": { + "clusterName": { + "type": "string", + "metadata": { + "description": "The resource name of the AKS cluster" + }, + "value": "[parameters('name')]" + }, + "clusterIdentity": { + "type": "object", + "metadata": { + "description": "The AKS cluster identity" + }, + "value": { + "clientId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.clientId]", + "objectId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.objectId]", + "resourceId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.resourceId]" + } + } + } + } + } + }, + { + "condition": "[variables('hasAgentPool')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aks-node-pool", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "name": { + "value": "npuserpool" + }, + "config": { + "value": "[union(createObject('name', 'npuser', 'mode', 'User'), variables('nodePoolBase'), variables('agentPoolSpec'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "6072270897511874144" + }, + "description": "Adds an agent pool to an Azure Kubernetes Service (AKS) cluster." + }, + "parameters": { + "clusterName": { + "type": "string" + }, + "name": { + "type": "string", + "metadata": { + "description": "The agent pool name" + } + }, + "config": { + "type": "object", + "metadata": { + "description": "The agent pool configuration" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters/agentPools", + "apiVersion": "2023-10-02-preview", + "name": "[format('{0}/{1}', parameters('clusterName'), parameters('name'))]", + "properties": "[parameters('config')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-registry", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerRegistryName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "workspaceId": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "12834334744516280883" + }, + "description": "Creates an Azure Container Registry." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "adminUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether admin user is enabled" + } + }, + "anonymousPullEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether anonymous pull is enabled" + } + }, + "dataEndpointEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether data endpoint is enabled" + } + }, + "encryption": { + "type": "object", + "defaultValue": { + "status": "disabled" + }, + "metadata": { + "description": "Encryption settings" + } + }, + "networkRuleBypassOptions": { + "type": "string", + "defaultValue": "AzureServices", + "metadata": { + "description": "Options for bypassing network rules" + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Public network access setting" + } + }, + "sku": { + "type": "object", + "defaultValue": { + "name": "Basic" + }, + "metadata": { + "description": "SKU settings" + } + }, + "zoneRedundancy": { + "type": "string", + "defaultValue": "Disabled", + "metadata": { + "description": "Zone redundancy setting" + } + }, + "workspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The log analytics workspace ID used for logging and monitoring" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2022-02-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": "[parameters('sku')]", + "properties": { + "adminUserEnabled": "[parameters('adminUserEnabled')]", + "anonymousPullEnabled": "[parameters('anonymousPullEnabled')]", + "dataEndpointEnabled": "[parameters('dataEndpointEnabled')]", + "encryption": "[parameters('encryption')]", + "networkRuleBypassOptions": "[parameters('networkRuleBypassOptions')]", + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "zoneRedundancy": "[parameters('zoneRedundancy')]" + } + }, + { + "condition": "[not(empty(parameters('workspaceId')))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "registry-diagnostics", + "properties": { + "workspaceId": "[parameters('workspaceId')]", + "logs": [ + { + "category": "ContainerRegistryRepositoryEvents", + "enabled": true + }, + { + "category": "ContainerRegistryLoginEvents", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true, + "timeGrain": "PT1M" + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" + ] + } + ], + "outputs": { + "loginServer": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), '2022-02-01-preview').loginServer]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cluster-container-registry-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerRegistryName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.name.value]" + }, + "principalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterIdentity.value.objectId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "15144906240959446537" + }, + "description": "Assigns ACR Pull permissions to access an Azure Container Registry." + }, + "parameters": { + "containerRegistryName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "acrPullRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('containerRegistryName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('acrPullRole'))]", + "properties": { + "roleDefinitionId": "[variables('acrPullRole')]", + "principalType": "ServicePrincipal", + "principalId": "[parameters('principalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'container-registry')]", + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + }, + { + "condition": "[or(parameters('enableAzureRbac'), parameters('disableLocalAccounts'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cluster-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "principalId": { + "value": "[parameters('principalId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8205013527918052324" + }, + "description": "Assigns RBAC role to the specified AKS cluster and principal." + }, + "parameters": { + "clusterName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "aksClusterAdminRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerService/managedClusters/{0}', parameters('clusterName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('aksClusterAdminRole'))]", + "properties": { + "roleDefinitionId": "[variables('aksClusterAdminRole')]", + "principalType": "User", + "principalId": "[parameters('principalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + } + ], + "outputs": { + "clusterName": { + "type": "string", + "metadata": { + "description": "The resource name of the AKS cluster" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "clusterIdentity": { + "type": "object", + "metadata": { + "description": "The AKS cluster identity" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterIdentity.value]" + }, + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The resource name of the ACR" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.name.value]" + }, + "containerRegistryLoginServer": { + "type": "string", + "metadata": { + "description": "The login server for the container registry" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.loginServer.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "monitoring", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "logAnalyticsName": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', parameters('logAnalyticsName')), createObject('value', format('{0}{1}', variables('abbrs').operationalInsightsWorkspaces, variables('resourceToken'))))]", + "applicationInsightsName": "[if(not(empty(parameters('applicationInsightsName'))), createObject('value', parameters('applicationInsightsName')), createObject('value', format('{0}{1}', variables('abbrs').insightsComponents, variables('resourceToken'))))]", + "applicationInsightsDashboardName": "[if(not(empty(parameters('applicationInsightsDashboardName'))), createObject('value', parameters('applicationInsightsDashboardName')), createObject('value', format('{0}{1}', variables('abbrs').portalDashboards, variables('resourceToken'))))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10041669792322197047" + }, + "description": "Creates an Application Insights instance and a Log Analytics workspace." + }, + "parameters": { + "logAnalyticsName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "loganalytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('logAnalyticsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "9622176141085970536" + }, + "description": "Creates a Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-12-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": 30, + "features": { + "searchVersion": 1 + }, + "sku": { + "name": "PerGB2018" + } + } + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "applicationinsights", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('applicationInsightsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "dashboardName": { + "value": "[parameters('applicationInsightsDashboardName')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1335628967363670282" + }, + "description": "Creates an Application Insights instance based on an existing Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "dashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "logAnalyticsWorkspaceId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]" + } + }, + { + "condition": "[not(empty(parameters('dashboardName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "application-insights-dashboard", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('dashboardName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "applicationInsightsName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "2145880658446193205" + }, + "description": "Creates a dashboard for an Application Insights instance." + }, + "parameters": { + "name": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Portal/dashboards", + "apiVersion": "2020-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "lenses": [ + { + "order": 0, + "parts": [ + { + "position": { + "x": 0, + "y": 0, + "colSpan": 2, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "id", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart", + "asset": { + "idInputName": "id", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "overview" + } + }, + { + "position": { + "x": 2, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "ProactiveDetection" + } + }, + { + "position": { + "x": 3, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + } + ], + "type": "Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:20:33.345Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 5, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-08T18:47:35.237Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "ConfigurationId", + "value": "78ce933e-e864-4b05-a27b-71fd55a6afad" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AppMapButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 0, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Usage", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 3, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:22:35.782Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + } + ], + "type": "Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Reliability", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 7, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:42:40.072Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "failures" + } + }, + { + "position": { + "x": 8, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Responsiveness\r\n", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 11, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:43:37.804Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "2a8ede4f-2bee-4b9c-aed9-2db0e8a01865", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "performance" + } + }, + { + "position": { + "x": 12, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Browser", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 15, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "MetricsExplorerJsonDefinitionId", + "value": "BrowserPerformanceTimelineMetrics" + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "createdTime": "2018-05-08T12:16:27.534Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "CurrentFilter", + "value": { + "eventTypes": [ + 4, + 1, + 3, + 5, + 2, + 6, + 13 + ], + "typeFacets": {}, + "isPermissive": false + } + }, + { + "name": "id", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "browser" + } + }, + { + "position": { + "x": 0, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "sessions/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Sessions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "users/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Users", + "color": "#7E58FF" + } + } + ], + "title": "Unique sessions and users", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "segmentationUsers" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Failed requests", + "color": "#EC008C" + } + } + ], + "title": "Failed requests", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "failures" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/duration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server response time", + "color": "#00BCF2" + } + } + ], + "title": "Server response time", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "performance" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/networkDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Page load network connect time", + "color": "#7E58FF" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/processingDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Client processing time", + "color": "#44F1C8" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/sendDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Send request time", + "color": "#EB9371" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/receiveDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Receiving response time", + "color": "#0672F1" + } + } + ], + "title": "Average page load time breakdown", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/availabilityPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability", + "color": "#47BDF5" + } + } + ], + "title": "Average availability", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "availability" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/server", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server exceptions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "dependencies/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Dependency failures", + "color": "#7E58FF" + } + } + ], + "title": "Server exceptions and Dependency failures", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processorCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Processor time", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process CPU", + "color": "#7E58FF" + } + } + ], + "title": "Average processor and process CPU utilization", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/browser", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Browser exceptions", + "color": "#47BDF5" + } + } + ], + "title": "Browser exceptions", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/count", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability test results count", + "color": "#47BDF5" + } + } + ], + "title": "Availability test results count", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processIOBytesPerSecond", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process IO rate", + "color": "#47BDF5" + } + } + ], + "title": "Average process I/O rate", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/memoryAvailableBytes", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Available memory", + "color": "#47BDF5" + } + } + ], + "title": "Average available memory", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + } + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').ConnectionString]" + }, + "instrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').InstrumentationKey]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'loganalytics')]" + ] + } + ], + "outputs": { + "applicationInsightsConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.connectionString.value]" + }, + "applicationInsightsInstrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "applicationInsightsName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.name.value]" + } + } + } + } + } + ], + "outputs": { + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "AZURE_LOCATION": { + "type": "string", + "value": "[parameters('location')]" + }, + "AZURE_TENANT_ID": { + "type": "string", + "value": "[tenant().tenantId]" + }, + "AZURE_AKS_CLUSTER_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.clusterName.value]" + }, + "AZURE_AKS_IDENTITY_CLIENT_ID": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.clusterIdentity.value.clientId]" + }, + "AZURE_CONTAINER_REGISTRY_ENDPOINT": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.containerRegistryLoginServer.value]" + }, + "AZURE_CONTAINER_REGISTRY_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.containerRegistryName.value]" + }, + "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/ai/cognitiveservices.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/ai/cognitiveservices.bicep new file mode 100644 index 00000000..1bf5666b --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/ai/cognitiveservices.bicep @@ -0,0 +1,53 @@ +metadata description = 'Creates an Azure Cognitive Services instance.' +param name string +param location string = resourceGroup().location +param tags object = {} +@description('The custom subdomain name used to access the API. Defaults to the value of the name parameter.') +param customSubDomainName string = name +param deployments array = [] +param kind string = 'OpenAI' + +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { + name: 'S0' +} + +param allowedIpRules array = [] +param networkAcls object = empty(allowedIpRules) ? { + defaultAction: 'Allow' +} : { + ipRules: allowedIpRules + defaultAction: 'Deny' +} + +resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + customSubDomainName: customSubDomainName + publicNetworkAccess: publicNetworkAccess + networkAcls: networkAcls + } + sku: sku +} + +@batchSize(1) +resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: { + parent: account + name: deployment.name + properties: { + model: deployment.model + raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null + } + sku: contains(deployment, 'sku') ? deployment.sku : { + name: 'Standard' + capacity: 20 + } +}] + +output endpoint string = account.properties.endpoint +output id string = account.id +output name string = account.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/cosmos-account.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/cosmos-account.bicep new file mode 100644 index 00000000..6f8747f5 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/cosmos-account.bicep @@ -0,0 +1,49 @@ +metadata description = 'Creates an Azure Cosmos DB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +@allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) +param kind string + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { + name: name + kind: kind + location: location + tags: tags + properties: { + consistencyPolicy: { defaultConsistencyLevel: 'Session' } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + databaseAccountOfferType: 'Standard' + enableAutomaticFailover: false + enableMultipleWriteLocations: false + apiProperties: (kind == 'MongoDB') ? { serverVersion: '4.2' } : {} + capabilities: [ { name: 'EnableServerless' } ] + } +} + +resource cosmosConnectionString 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: cosmos.listConnectionStrings().connectionStrings[0].connectionString + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output connectionStringKey string = connectionStringKey +output endpoint string = cosmos.properties.documentEndpoint +output id string = cosmos.id +output name string = cosmos.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/mongo/cosmos-mongo-account.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/mongo/cosmos-mongo-account.bicep new file mode 100644 index 00000000..4aafbf38 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/mongo/cosmos-mongo-account.bicep @@ -0,0 +1,23 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + connectionStringKey: connectionStringKey + keyVaultName: keyVaultName + kind: 'MongoDB' + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/mongo/cosmos-mongo-db.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/mongo/cosmos-mongo-db.bicep new file mode 100644 index 00000000..2a670578 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/mongo/cosmos-mongo-db.bicep @@ -0,0 +1,47 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [] +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +module cosmos 'cosmos-mongo-account.bicep' = { + name: 'cosmos-mongo-account' + params: { + name: accountName + location: location + keyVaultName: keyVaultName + tags: tags + connectionStringKey: connectionStringKey + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2022-08-15' = { + name: '${accountName}/${databaseName}' + tags: tags + properties: { + resource: { id: databaseName } + } + + resource list 'collections' = [for collection in collections: { + name: collection.name + properties: { + resource: { + id: collection.id + shardKey: { _id: collection.shardKey } + indexes: [ { key: { keys: [ collection.indexKey ] } } ] + } + } + }] + + dependsOn: [ + cosmos + ] +} + +output connectionStringKey string = connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-account.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-account.bicep new file mode 100644 index 00000000..8431135e --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-account.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + tags: tags + keyVaultName: keyVaultName + kind: 'GlobalDocumentDB' + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id +output name string = cosmos.outputs.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-db.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-db.bicep new file mode 100644 index 00000000..265880dc --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-db.bicep @@ -0,0 +1,74 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param containers array = [] +param keyVaultName string +param principalIds array = [] + +module cosmos 'cosmos-sql-account.bicep' = { + name: 'cosmos-sql-account' + params: { + name: accountName + location: location + tags: tags + keyVaultName: keyVaultName + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = { + name: '${accountName}/${databaseName}' + properties: { + resource: { id: databaseName } + } + + resource list 'containers' = [for container in containers: { + name: container.name + properties: { + resource: { + id: container.id + partitionKey: { paths: [ container.partitionKey ] } + } + options: {} + } + }] + + dependsOn: [ + cosmos + ] +} + +module roleDefinition 'cosmos-sql-role-def.bicep' = { + name: 'cosmos-sql-role-definition' + params: { + accountName: accountName + } + dependsOn: [ + cosmos + database + ] +} + +// We need batchSize(1) here because sql role assignments have to be done sequentially +@batchSize(1) +module userRole 'cosmos-sql-role-assign.bicep' = [for principalId in principalIds: if (!empty(principalId)) { + name: 'cosmos-sql-user-role-${uniqueString(principalId)}' + params: { + accountName: accountName + roleDefinitionId: roleDefinition.outputs.id + principalId: principalId + } + dependsOn: [ + cosmos + database + ] +}] + +output accountId string = cosmos.outputs.id +output accountName string = cosmos.outputs.name +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint +output roleDefinitionId string = roleDefinition.outputs.id diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-role-assign.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-role-assign.bicep new file mode 100644 index 00000000..3949efef --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-role-assign.bicep @@ -0,0 +1,19 @@ +metadata description = 'Creates a SQL role assignment under an Azure Cosmos DB account.' +param accountName string + +param roleDefinitionId string +param principalId string = '' + +resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { + parent: cosmos + name: guid(roleDefinitionId, principalId, cosmos.id) + properties: { + principalId: principalId + roleDefinitionId: roleDefinitionId + scope: cosmos.id + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-role-def.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-role-def.bicep new file mode 100644 index 00000000..778d6dc4 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/cosmos/sql/cosmos-sql-role-def.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates a SQL role definition under an Azure Cosmos DB account.' +param accountName string + +resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2022-08-15' = { + parent: cosmos + name: guid(cosmos.id, accountName, 'sql-role') + properties: { + assignableScopes: [ + cosmos.id + ] + permissions: [ + { + dataActions: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + ] + notDataActions: [] + } + ] + roleName: 'Reader Writer' + type: 'CustomRole' + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} + +output id string = roleDefinition.id diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/database/postgresql/flexibleserver.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/postgresql/flexibleserver.bicep new file mode 100644 index 00000000..7e26b1a8 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/postgresql/flexibleserver.bicep @@ -0,0 +1,65 @@ +metadata description = 'Creates an Azure Database for PostgreSQL - Flexible Server.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object +param storage object +param administratorLogin string +@secure() +param administratorLoginPassword string +param databaseNames array = [] +param allowAzureIPsFirewall bool = false +param allowAllIPsFirewall bool = false +param allowedSingleIPs array = [] + +// PostgreSQL version +param version string + +// Latest official version 2022-12-01 does not have Bicep types available +resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = { + location: location + tags: tags + name: name + sku: sku + properties: { + version: version + administratorLogin: administratorLogin + administratorLoginPassword: administratorLoginPassword + storage: storage + highAvailability: { + mode: 'Disabled' + } + } + + resource database 'databases' = [for name in databaseNames: { + name: name + }] + + resource firewall_all 'firewallRules' = if (allowAllIPsFirewall) { + name: 'allow-all-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '255.255.255.255' + } + } + + resource firewall_azure 'firewallRules' = if (allowAzureIPsFirewall) { + name: 'allow-all-azure-internal-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '0.0.0.0' + } + } + + resource firewall_single 'firewallRules' = [for ip in allowedSingleIPs: { + name: 'allow-single-${replace(ip, '.', '')}' + properties: { + startIpAddress: ip + endIpAddress: ip + } + }] + +} + +output POSTGRES_DOMAIN_NAME string = postgresServer.properties.fullyQualifiedDomainName diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/database/sqlserver/sqlserver.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/sqlserver/sqlserver.bicep new file mode 100644 index 00000000..84f2cc2c --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/database/sqlserver/sqlserver.bicep @@ -0,0 +1,130 @@ +metadata description = 'Creates an Azure SQL Server instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param appUser string = 'appUser' +param databaseName string +param keyVaultName string +param sqlAdmin string = 'sqlAdmin' +param connectionStringKey string = 'AZURE-SQL-CONNECTION-STRING' + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name + location: location + tags: tags + properties: { + version: '12.0' + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + administratorLogin: sqlAdmin + administratorLoginPassword: sqlAdminPassword + } + + resource database 'databases' = { + name: databaseName + location: location + } + + resource firewall 'firewallRules' = { + name: 'Azure Services' + properties: { + // Allow all clients + // Note: range [0.0.0.0-0.0.0.0] means "allow all Azure-hosted clients only". + // This is not sufficient, because we also want to allow direct access from developer machine, for debugging purposes. + startIpAddress: '0.0.0.1' + endIpAddress: '255.255.255.254' + } + } +} + +resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: '${name}-deployment-script' + location: location + kind: 'AzureCLI' + properties: { + azCliVersion: '2.37.0' + retentionInterval: 'PT1H' // Retain the script resource for 1 hour after it ends running + timeout: 'PT5M' // Five minutes + cleanupPreference: 'OnSuccess' + environmentVariables: [ + { + name: 'APPUSERNAME' + value: appUser + } + { + name: 'APPUSERPASSWORD' + secureValue: appUserPassword + } + { + name: 'DBNAME' + value: databaseName + } + { + name: 'DBSERVER' + value: sqlServer.properties.fullyQualifiedDomainName + } + { + name: 'SQLCMDPASSWORD' + secureValue: sqlAdminPassword + } + { + name: 'SQLADMIN' + value: sqlAdmin + } + ] + + scriptContent: ''' +wget https://github.com/microsoft/go-sqlcmd/releases/download/v0.8.1/sqlcmd-v0.8.1-linux-x64.tar.bz2 +tar x -f sqlcmd-v0.8.1-linux-x64.tar.bz2 -C . + +cat < ./initDb.sql +drop user if exists ${APPUSERNAME} +go +create user ${APPUSERNAME} with password = '${APPUSERPASSWORD}' +go +alter role db_owner add member ${APPUSERNAME} +go +SCRIPT_END + +./sqlcmd -S ${DBSERVER} -d ${DBNAME} -U ${SQLADMIN} -i ./initDb.sql + ''' + } +} + +resource sqlAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'sqlAdminPassword' + properties: { + value: sqlAdminPassword + } +} + +resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'appUserPassword' + properties: { + value: appUserPassword + } +} + +resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: '${connectionString}; Password=${appUserPassword}' + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output connectionStringKey string = connectionStringKey +output databaseName string = sqlServer::database.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/gateway/apim.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/gateway/apim.bicep new file mode 100644 index 00000000..be7464f0 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/gateway/apim.bicep @@ -0,0 +1,79 @@ +metadata description = 'Creates an Azure API Management instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The email address of the owner of the service') +@minLength(1) +param publisherEmail string = 'noreply@microsoft.com' + +@description('The name of the owner of the service') +@minLength(1) +param publisherName string = 'n/a' + +@description('The pricing tier of this API Management service') +@allowed([ + 'Consumption' + 'Developer' + 'Standard' + 'Premium' +]) +param sku string = 'Consumption' + +@description('The instance size of this API Management service.') +@allowed([ 0, 1, 2 ]) +param skuCount int = 0 + +@description('Azure Application Insights Name') +param applicationInsightsName string + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': name }) + sku: { + name: sku + capacity: (sku == 'Consumption') ? 0 : ((sku == 'Developer') ? 1 : skuCount) + } + properties: { + publisherEmail: publisherEmail + publisherName: publisherName + // Custom properties are not supported for Consumption SKU + customProperties: sku == 'Consumption' ? {} : { + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_GCM_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TripleDes168': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Ssl30': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30': 'false' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' = if (!empty(applicationInsightsName)) { + name: 'app-insights-logger' + parent: apimService + properties: { + credentials: { + instrumentationKey: applicationInsights.properties.InstrumentationKey + } + description: 'Logger to Azure Application Insights' + isBuffered: false + loggerType: 'applicationInsights' + resourceId: applicationInsights.id + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output apimServiceName string = apimService.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/aks-agent-pool.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/aks-agent-pool.bicep new file mode 100644 index 00000000..9c764358 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/aks-agent-pool.bicep @@ -0,0 +1,18 @@ +metadata description = 'Adds an agent pool to an Azure Kubernetes Service (AKS) cluster.' +param clusterName string + +@description('The agent pool name') +param name string + +@description('The agent pool configuration') +param config object + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} + +resource nodePool 'Microsoft.ContainerService/managedClusters/agentPools@2023-10-02-preview' = { + parent: aksCluster + name: name + properties: config +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/aks-managed-cluster.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/aks-managed-cluster.bicep new file mode 100644 index 00000000..de562a66 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/aks-managed-cluster.bicep @@ -0,0 +1,140 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// AAD Integration +@description('Enable Azure Active Directory integration') +param enableAad bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('Configuration of AKS add-ons') +param addOns object = {} + +@description('The log analytics workspace id used for logging & monitoring') +param workspaceId string = '' + +@description('The node pool configuration for the System agent pool') +param systemPoolConfig object + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +resource aks 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' = { + name: name + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + sku: { + name: 'Base' + tier: sku + } + properties: { + nodeResourceGroup: !empty(nodeResourceGroupName) ? nodeResourceGroupName : 'rg-mc-${name}' + kubernetesVersion: kubernetesVersion + dnsPrefix: empty(dnsPrefix) ? '${name}-dns' : dnsPrefix + enableRBAC: enableRbac + aadProfile: enableAad ? { + managed: true + enableAzureRBAC: enableAzureRbac + tenantID: aadTenantId + } : null + agentPoolProfiles: [ + systemPoolConfig + ] + networkProfile: { + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } + disableLocalAccounts: disableLocalAccounts && enableAad + addonProfiles: addOns + ingressProfile: { + webAppRouting: { + enabled: webAppRoutingAddon + } + } + } +} + +var aksDiagCategories = [ + 'cluster-autoscaler' + 'kube-controller-manager' + 'kube-audit-admin' + 'guard' +] + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'aks-diagnostics' + scope: aks + properties: { + workspaceId: workspaceId + logs: [for category in aksDiagCategories: { + category: category + enabled: true + }] + metrics: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +} + +@description('The resource name of the AKS cluster') +output clusterName string = aks.name + +@description('The AKS cluster identity') +output clusterIdentity object = { + clientId: aks.properties.identityProfile.kubeletidentity.clientId + objectId: aks.properties.identityProfile.kubeletidentity.objectId + resourceId: aks.properties.identityProfile.kubeletidentity.resourceId +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/aks.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/aks.bicep new file mode 100644 index 00000000..4a36262c --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/aks.bicep @@ -0,0 +1,268 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool as well as an additional user agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name for the Azure container registry (ACR)') +param containerRegistryName string + +@description('The name of the connected log analytics workspace') +param logAnalyticsName string = '' + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('AKS add-ons configuration') +param addOns object = { + azurePolicy: { + enabled: true + config: { + version: 'v2' + } + } + keyVault: { + enabled: true + config: { + enableSecretRotation: 'true' + rotationPollInterval: '2m' + } + } + openServiceMesh: { + enabled: false + config: {} + } + omsAgent: { + enabled: true + config: {} + } + applicationGateway: { + enabled: false + config: {} + } +} + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@allowed([ + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The System Pool Preset sizing') +param systemPoolType string = 'CostOptimised' + +@allowed([ + '' + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The User Pool Preset sizing') +param agentPoolType string = '' + +// Configure system / user agent pools +@description('Custom configuration of system node pool') +param systemPoolConfig object = {} +@description('Custom configuration of user node pool') +param agentPoolConfig object = {} + +@description('Id of the user or app to assign application roles') +param principalId string = '' + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// Configure AKS add-ons +var omsAgentConfig = (!empty(logAnalyticsName) && !empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? union( + addOns.omsAgent, + { + config: { + logAnalyticsWorkspaceResourceID: logAnalytics.id + } + } +) : {} + +var addOnsConfig = union( + (!empty(addOns.azurePolicy) && addOns.azurePolicy.enabled) ? { azurepolicy: addOns.azurePolicy } : {}, + (!empty(addOns.keyVault) && addOns.keyVault.enabled) ? { azureKeyvaultSecretsProvider: addOns.keyVault } : {}, + (!empty(addOns.openServiceMesh) && addOns.openServiceMesh.enabled) ? { openServiceMesh: addOns.openServiceMesh } : {}, + (!empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? { omsagent: omsAgentConfig } : {}, + (!empty(addOns.applicationGateway) && addOns.applicationGateway.enabled) ? { ingressApplicationGateway: addOns.applicationGateway } : {} +) + +// Link to existing log analytics workspace when available +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' existing = if (!empty(logAnalyticsName)) { + name: logAnalyticsName +} + +var systemPoolSpec = !empty(systemPoolConfig) ? systemPoolConfig : nodePoolPresets[systemPoolType] + +// Create the primary AKS cluster resources and system node pool +module managedCluster 'aks-managed-cluster.bicep' = { + name: 'managed-cluster' + params: { + name: name + location: location + tags: tags + systemPoolConfig: union( + { name: 'npsystem', mode: 'System' }, + nodePoolBase, + systemPoolSpec + ) + nodeResourceGroupName: nodeResourceGroupName + sku: sku + dnsPrefix: dnsPrefix + kubernetesVersion: kubernetesVersion + addOns: addOnsConfig + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + enableAad: enableAzureRbac && aadTenantId != '' + disableLocalAccounts: disableLocalAccounts + aadTenantId: aadTenantId + enableRbac: enableRbac + enableAzureRbac: enableAzureRbac + webAppRoutingAddon: webAppRoutingAddon + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } +} + +var hasAgentPool = !empty(agentPoolConfig) || !empty(agentPoolType) +var agentPoolSpec = hasAgentPool && !empty(agentPoolConfig) ? agentPoolConfig : empty(agentPoolType) ? {} : nodePoolPresets[agentPoolType] + +// Create additional user agent pool when specified +module agentPool 'aks-agent-pool.bicep' = if (hasAgentPool) { + name: 'aks-node-pool' + params: { + clusterName: managedCluster.outputs.clusterName + name: 'npuserpool' + config: union({ name: 'npuser', mode: 'User' }, nodePoolBase, agentPoolSpec) + } +} + +// Creates container registry (ACR) +module containerRegistry 'container-registry.bicep' = { + name: 'container-registry' + params: { + name: containerRegistryName + location: location + tags: tags + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + } +} + +// Grant ACR Pull access from cluster managed identity to container registry +module containerRegistryAccess '../security/registry-access.bicep' = { + name: 'cluster-container-registry-access' + params: { + containerRegistryName: containerRegistry.outputs.name + principalId: managedCluster.outputs.clusterIdentity.objectId + } +} + +// Give AKS cluster access to the specified principal +module clusterAccess '../security/aks-managed-cluster-access.bicep' = if (enableAzureRbac || disableLocalAccounts) { + name: 'cluster-access' + params: { + clusterName: managedCluster.outputs.clusterName + principalId: principalId + } +} + +// Helpers for node pool configuration +var nodePoolBase = { + osType: 'Linux' + maxPods: 30 + type: 'VirtualMachineScaleSets' + upgradeSettings: { + maxSurge: '33%' + } +} + +var nodePoolPresets = { + CostOptimised: { + vmSize: 'Standard_B4ms' + count: 1 + minCount: 1 + maxCount: 3 + enableAutoScaling: true + availabilityZones: [] + } + Standard: { + vmSize: 'Standard_DS2_v2' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } + HighSpec: { + vmSize: 'Standard_D4s_v3' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } +} + +// Module outputs +@description('The resource name of the AKS cluster') +output clusterName string = managedCluster.outputs.clusterName + +@description('The AKS cluster identity') +output clusterIdentity object = managedCluster.outputs.clusterIdentity + +@description('The resource name of the ACR') +output containerRegistryName string = containerRegistry.outputs.name + +@description('The login server for the container registry') +output containerRegistryLoginServer string = containerRegistry.outputs.loginServer diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/appservice-appsettings.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/appservice-appsettings.bicep new file mode 100644 index 00000000..f4b22f81 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/appservice-appsettings.bicep @@ -0,0 +1,17 @@ +metadata description = 'Updates app settings for an Azure App Service.' +@description('The name of the app service resource within the current resource group scope') +param name string + +@description('The app settings to be applied to the app service') +@secure() +param appSettings object + +resource appService 'Microsoft.Web/sites@2022-03-01' existing = { + name: name +} + +resource settings 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'appsettings' + parent: appService + properties: appSettings +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/appservice.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/appservice.bicep new file mode 100644 index 00000000..bef4d2ba --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/appservice.bicep @@ -0,0 +1,123 @@ +metadata description = 'Creates an Azure App Service in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Microsoft.Web/sites Properties +param kind string = 'app,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = false +param use32BitWorkerProcess bool = false +param ftpsState string = 'FtpsOnly' +param healthCheckPath string = '' + +resource appService 'Microsoft.Web/sites@2022-03-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + serverFarmId: appServicePlanId + siteConfig: { + linuxFxVersion: linuxFxVersion + alwaysOn: alwaysOn + ftpsState: ftpsState + minTlsVersion: '1.2' + appCommandLine: appCommandLine + numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null + minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null + use32BitWorkerProcess: use32BitWorkerProcess + functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null + healthCheckPath: healthCheckPath + cors: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: true + } + + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + + resource basicPublishingCredentialsPoliciesFtp 'basicPublishingCredentialsPolicies' = { + name: 'ftp' + properties: { + allow: false + } + } + + resource basicPublishingCredentialsPoliciesScm 'basicPublishingCredentialsPolicies' = { + name: 'scm' + properties: { + allow: false + } + } +} + +// Updates to the single Microsoft.sites/web/config resources that need to be performed sequentially +// sites/web/config 'appsettings' +module configAppSettings 'appservice-appsettings.bicep' = { + name: '${name}-appSettings' + params: { + name: appService.name + appSettings: union(appSettings, + { + SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) + ENABLE_ORYX_BUILD: string(enableOryxBuild) + }, + runtimeName == 'python' && appCommandLine == '' ? { PYTHON_ENABLE_GUNICORN_MULTIWORKERS: 'true'} : {}, + !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, + !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) + } +} + +// sites/web/config 'logs' +resource configLogs 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'logs' + parent: appService + properties: { + applicationLogs: { fileSystem: { level: 'Verbose' } } + detailedErrorMessages: { enabled: true } + failedRequestsTracing: { enabled: true } + httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } + } + dependsOn: [configAppSettings] +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) { + name: keyVaultName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output identityPrincipalId string = managedIdentity ? appService.identity.principalId : '' +output name string = appService.name +output uri string = 'https://${appService.properties.defaultHostName}' diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/appserviceplan.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/appserviceplan.bicep new file mode 100644 index 00000000..2e37e041 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/appserviceplan.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param kind string = '' +param reserved bool = true +param sku object + +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + kind: kind + properties: { + reserved: reserved + } +} + +output id string = appServicePlan.id +output name string = appServicePlan.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-app-upsert.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-app-upsert.bicep new file mode 100644 index 00000000..3eec62f2 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-app-upsert.bicep @@ -0,0 +1,105 @@ +metadata description = 'Creates or updates an existing Azure Container App.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The environment name for the container apps') +param containerAppsEnvironmentName string + +@description('The number of CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('The amount of memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@allowed([ 'http', 'grpc' ]) +@description('The protocol used by Dapr to connect to the app, e.g., HTTP or gRPC') +param daprAppProtocol string = 'http' + +@description('Enable or disable Dapr for the container app') +param daprEnabled bool = false + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Specifies if the resource already exists') +param exists bool = false + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The name of the container image') +param imageName string = '' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The target port for the container') +param targetPort int = 80 + +resource existingApp 'Microsoft.App/containerApps@2023-04-01-preview' existing = if (exists) { + name: name +} + +module app 'container-app.bicep' = { + name: '${deployment().name}-update' + params: { + name: name + location: location + tags: tags + identityType: identityType + identityName: identityName + ingressEnabled: ingressEnabled + containerName: containerName + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: containerCpuCoreCount + containerMemory: containerMemory + containerMinReplicas: containerMinReplicas + containerMaxReplicas: containerMaxReplicas + daprEnabled: daprEnabled + daprAppId: daprAppId + daprAppProtocol: daprAppProtocol + secrets: secrets + external: external + env: env + imageName: !empty(imageName) ? imageName : exists ? existingApp.properties.template.containers[0].image : '' + targetPort: targetPort + serviceBinds: serviceBinds + } +} + +output defaultDomain string = app.outputs.defaultDomain +output imageName string = app.outputs.imageName +output name string = app.outputs.name +output uri string = app.outputs.uri diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-app.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-app.bicep new file mode 100644 index 00000000..3724086d --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-app.bicep @@ -0,0 +1,162 @@ +metadata description = 'Creates a container app in an Azure Container App environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Allowed origins') +param allowedOrigins array = [] + +@description('Name of the environment for container apps') +param containerAppsEnvironmentName string + +@description('CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('Memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@description('The protocol used by Dapr to connect to the app, e.g., http or grpc') +@allowed([ 'http', 'grpc' ]) +param daprAppProtocol string = 'http' + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Enable Dapr') +param daprEnabled bool = false + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the container image') +param imageName string = '' + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +param revisionMode string = 'Single' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The name of the container apps add-on to use. e.g. redis') +param serviceType string = '' + +@description('The target port for the container') +param targetPort int = 80 + +resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(identityName)) { + name: identityName +} + +// Private registry support requires both an ACR name and a User Assigned managed identity +var usePrivateRegistry = !empty(identityName) && !empty(containerRegistryName) + +// Automatically set to `UserAssigned` when an `identityName` has been set +var normalizedIdentityType = !empty(identityName) ? 'UserAssigned' : identityType + +module containerRegistryAccess '../security/registry-access.bicep' = if (usePrivateRegistry) { + name: '${deployment().name}-registry-access' + params: { + containerRegistryName: containerRegistryName + principalId: usePrivateRegistry ? userIdentity.properties.principalId : '' + } +} + +resource app 'Microsoft.App/containerApps@2023-04-01-preview' = { + name: name + location: location + tags: tags + // It is critical that the identity is granted ACR pull access before the app is created + // otherwise the container app will throw a provision error + // This also forces us to use an user assigned managed identity since there would no way to + // provide the system assigned identity with the ACR pull access before the app is created + dependsOn: usePrivateRegistry ? [ containerRegistryAccess ] : [] + identity: { + type: normalizedIdentityType + userAssignedIdentities: !empty(identityName) && normalizedIdentityType == 'UserAssigned' ? { '${userIdentity.id}': {} } : null + } + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + activeRevisionsMode: revisionMode + ingress: ingressEnabled ? { + external: external + targetPort: targetPort + transport: 'auto' + corsPolicy: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } : null + dapr: daprEnabled ? { + enabled: true + appId: daprAppId + appProtocol: daprAppProtocol + appPort: ingressEnabled ? targetPort : 0 + } : { enabled: false } + secrets: secrets + service: !empty(serviceType) ? { type: serviceType } : null + registries: usePrivateRegistry ? [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userIdentity.id + } + ] : [] + } + template: { + serviceBinds: !empty(serviceBinds) ? serviceBinds : null + containers: [ + { + image: !empty(imageName) ? imageName : 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: containerName + env: env + resources: { + cpu: json(containerCpuCoreCount) + memory: containerMemory + } + } + ] + scale: { + minReplicas: containerMinReplicas + maxReplicas: containerMaxReplicas + } + } + } +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' existing = { + name: containerAppsEnvironmentName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output identityPrincipalId string = normalizedIdentityType == 'None' ? '' : (empty(identityName) ? app.identity.principalId : userIdentity.properties.principalId) +output imageName string = imageName +output name string = app.name +output serviceBind object = !empty(serviceType) ? { serviceId: app.id, name: name } : {} +output uri string = ingressEnabled ? 'https://${app.properties.configuration.ingress.fqdn}' : '' diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-apps-environment.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-apps-environment.bicep new file mode 100644 index 00000000..8633ba48 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-apps-environment.bicep @@ -0,0 +1,41 @@ +metadata description = 'Creates an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the Application Insights resource') +param applicationInsightsName string = '' + +@description('Specifies if Dapr is enabled') +param daprEnabled bool = false + +@description('Name of the Log Analytics workspace') +param logAnalyticsWorkspaceName string + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: logAnalyticsWorkspaceName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output id string = containerAppsEnvironment.id +output name string = containerAppsEnvironment.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-apps.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-apps.bicep new file mode 100644 index 00000000..1c656e28 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-apps.bicep @@ -0,0 +1,40 @@ +metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param containerRegistryName string +param containerRegistryResourceGroupName string = '' +param containerRegistryAdminUserEnabled bool = false +param logAnalyticsWorkspaceName string +param applicationInsightsName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${name}-container-apps-environment' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + logAnalyticsWorkspaceName: logAnalyticsWorkspaceName + applicationInsightsName: applicationInsightsName + } +} + +module containerRegistry 'container-registry.bicep' = { + name: '${name}-container-registry' + scope: !empty(containerRegistryResourceGroupName) ? resourceGroup(containerRegistryResourceGroupName) : resourceGroup() + params: { + name: containerRegistryName + location: location + adminUserEnabled: containerRegistryAdminUserEnabled + tags: tags + } +} + +output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output environmentName string = containerAppsEnvironment.outputs.name +output environmentId string = containerAppsEnvironment.outputs.id + +output registryLoginServer string = containerRegistry.outputs.loginServer +output registryName string = containerRegistry.outputs.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-registry.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-registry.bicep new file mode 100644 index 00000000..9c64531b --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/container-registry.bicep @@ -0,0 +1,83 @@ +metadata description = 'Creates an Azure Container Registry.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Indicates whether admin user is enabled') +param adminUserEnabled bool = false + +@description('Indicates whether anonymous pull is enabled') +param anonymousPullEnabled bool = false + +@description('Indicates whether data endpoint is enabled') +param dataEndpointEnabled bool = false + +@description('Encryption settings') +param encryption object = { + status: 'disabled' +} + +@description('Options for bypassing network rules') +param networkRuleBypassOptions string = 'AzureServices' + +@description('Public network access setting') +param publicNetworkAccess string = 'Enabled' + +@description('SKU settings') +param sku object = { + name: 'Basic' +} + +@description('Zone redundancy setting') +param zoneRedundancy string = 'Disabled' + +@description('The log analytics workspace ID used for logging and monitoring') +param workspaceId string = '' + +// 2022-02-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { + name: name + location: location + tags: tags + sku: sku + properties: { + adminUserEnabled: adminUserEnabled + anonymousPullEnabled: anonymousPullEnabled + dataEndpointEnabled: dataEndpointEnabled + encryption: encryption + networkRuleBypassOptions: networkRuleBypassOptions + publicNetworkAccess: publicNetworkAccess + zoneRedundancy: zoneRedundancy + } +} + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'registry-diagnostics' + scope: containerRegistry + properties: { + workspaceId: workspaceId + logs: [ + { + category: 'ContainerRegistryRepositoryEvents' + enabled: true + } + { + category: 'ContainerRegistryLoginEvents' + enabled: true + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + timeGrain: 'PT1M' + } + ] + } +} + +output loginServer string = containerRegistry.properties.loginServer +output name string = containerRegistry.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/functions.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/functions.bicep new file mode 100644 index 00000000..7070a2c6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/functions.bicep @@ -0,0 +1,86 @@ +metadata description = 'Creates an Azure Function in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) +param storageAccountName string + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Function Settings +@allowed([ + '~4', '~3', '~2', '~1' +]) +param extensionVersion string = '~4' + +// Microsoft.Web/sites Properties +param kind string = 'functionapp,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = true +param use32BitWorkerProcess bool = false +param healthCheckPath string = '' + +module functions 'appservice.bicep' = { + name: '${name}-functions' + params: { + name: name + location: location + tags: tags + allowedOrigins: allowedOrigins + alwaysOn: alwaysOn + appCommandLine: appCommandLine + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: union(appSettings, { + AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${storage.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' + FUNCTIONS_EXTENSION_VERSION: extensionVersion + FUNCTIONS_WORKER_RUNTIME: runtimeName + }) + clientAffinityEnabled: clientAffinityEnabled + enableOryxBuild: enableOryxBuild + functionAppScaleLimit: functionAppScaleLimit + healthCheckPath: healthCheckPath + keyVaultName: keyVaultName + kind: kind + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + minimumElasticInstanceCount: minimumElasticInstanceCount + numberOfWorkers: numberOfWorkers + runtimeName: runtimeName + runtimeVersion: runtimeVersion + runtimeNameAndVersion: runtimeNameAndVersion + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + use32BitWorkerProcess: use32BitWorkerProcess + } +} + +resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { + name: storageAccountName +} + +output identityPrincipalId string = managedIdentity ? functions.outputs.identityPrincipalId : '' +output name string = functions.outputs.name +output uri string = functions.outputs.uri diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/host/staticwebapp.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/staticwebapp.bicep new file mode 100644 index 00000000..cedaf906 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/host/staticwebapp.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Static Web Apps instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'Free' + tier: 'Free' +} + +resource web 'Microsoft.Web/staticSites@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + properties: { + provider: 'Custom' + } +} + +output name string = web.name +output uri string = 'https://${web.properties.defaultHostname}' diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/applicationinsights-dashboard.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/applicationinsights-dashboard.bicep new file mode 100644 index 00000000..d082e668 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/applicationinsights-dashboard.bicep @@ -0,0 +1,1236 @@ +metadata description = 'Creates a dashboard for an Application Insights instance.' +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/applicationinsights.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/applicationinsights.bicep new file mode 100644 index 00000000..4b4d01e3 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/applicationinsights.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' +param name string +param dashboardName string = '' +param location string = resourceGroup().location +param tags object = {} +param logAnalyticsWorkspaceId string + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { + name: 'application-insights-dashboard' + params: { + name: dashboardName + location: location + applicationInsightsName: applicationInsights.name + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/loganalytics.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/loganalytics.bicep new file mode 100644 index 00000000..33f9dc29 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/loganalytics.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates a Log Analytics workspace.' +param name string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: name + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/monitoring.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/monitoring.bicep new file mode 100644 index 00000000..6bb05b0b --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/monitor/monitoring.bicep @@ -0,0 +1,32 @@ +metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string = '' +param location string = resourceGroup().location +param tags object = {} + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + name: logAnalyticsName + location: location + tags: tags + } +} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/networking/cdn-endpoint.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/networking/cdn-endpoint.bicep new file mode 100644 index 00000000..5e8ab695 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/networking/cdn-endpoint.bicep @@ -0,0 +1,52 @@ +metadata description = 'Adds an endpoint to an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The name of the CDN profile resource') +@minLength(1) +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('The origin URL for the endpoint') +@minLength(1) +param originUrl string + +resource endpoint 'Microsoft.Cdn/profiles/endpoints@2022-05-01-preview' = { + parent: cdnProfile + name: name + location: location + tags: tags + properties: { + originHostHeader: originUrl + isHttpAllowed: false + isHttpsAllowed: true + queryStringCachingBehavior: 'UseQueryString' + optimizationType: 'GeneralWebDelivery' + origins: [ + { + name: replace(originUrl, '.', '-') + properties: { + hostName: originUrl + originHostHeader: originUrl + priority: 1 + weight: 1000 + enabled: true + } + } + ] + deliveryPolicy: { + rules: deliveryPolicyRules + } + } +} + +resource cdnProfile 'Microsoft.Cdn/profiles@2022-05-01-preview' existing = { + name: cdnProfileName +} + +output id string = endpoint.id +output name string = endpoint.name +output uri string = 'https://${endpoint.properties.hostName}' diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/networking/cdn-profile.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/networking/cdn-profile.bicep new file mode 100644 index 00000000..27669ee2 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/networking/cdn-profile.bicep @@ -0,0 +1,34 @@ +metadata description = 'Creates an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The pricing tier of this CDN profile') +@allowed([ + 'Custom_Verizon' + 'Premium_AzureFrontDoor' + 'Premium_Verizon' + 'StandardPlus_955BandWidth_ChinaCdn' + 'StandardPlus_AvgBandWidth_ChinaCdn' + 'StandardPlus_ChinaCdn' + 'Standard_955BandWidth_ChinaCdn' + 'Standard_Akamai' + 'Standard_AvgBandWidth_ChinaCdn' + 'Standard_AzureFrontDoor' + 'Standard_ChinaCdn' + 'Standard_Microsoft' + 'Standard_Verizon' +]) +param sku string = 'Standard_Microsoft' + +resource profile 'Microsoft.Cdn/profiles@2022-05-01-preview' = { + name: name + location: location + tags: tags + sku: { + name: sku + } +} + +output id string = profile.id +output name string = profile.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/networking/cdn.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/networking/cdn.bicep new file mode 100644 index 00000000..de98a1f9 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/networking/cdn.bicep @@ -0,0 +1,42 @@ +metadata description = 'Creates an Azure CDN profile with a single endpoint.' +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the CDN endpoint resource') +param cdnEndpointName string + +@description('Name of the CDN profile resource') +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('Origin URL for the CDN endpoint') +param originUrl string + +module cdnProfile 'cdn-profile.bicep' = { + name: 'cdn-profile' + params: { + name: cdnProfileName + location: location + tags: tags + } +} + +module cdnEndpoint 'cdn-endpoint.bicep' = { + name: 'cdn-endpoint' + params: { + name: cdnEndpointName + location: location + tags: tags + cdnProfileName: cdnProfile.outputs.name + originUrl: originUrl + deliveryPolicyRules: deliveryPolicyRules + } +} + +output endpointName string = cdnEndpoint.outputs.name +output endpointId string = cdnEndpoint.outputs.id +output profileName string = cdnProfile.outputs.name +output profileId string = cdnProfile.outputs.id +output uri string = cdnEndpoint.outputs.uri diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/search/search-services.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/search/search-services.bicep new file mode 100644 index 00000000..d9c619a9 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/search/search-services.bicep @@ -0,0 +1,68 @@ +metadata description = 'Creates an Azure AI Search instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'standard' +} + +param authOptions object = {} +param disableLocalAuth bool = false +param disabledDataExfiltrationOptions array = [] +param encryptionWithCmk object = { + enforcement: 'Unspecified' +} +@allowed([ + 'default' + 'highDensity' +]) +param hostingMode string = 'default' +param networkRuleSet object = { + bypass: 'None' + ipRules: [] +} +param partitionCount int = 1 +@allowed([ + 'enabled' + 'disabled' +]) +param publicNetworkAccess string = 'enabled' +param replicaCount int = 1 +@allowed([ + 'disabled' + 'free' + 'standard' +]) +param semanticSearch string = 'disabled' + +var searchIdentityProvider = (sku.name == 'free') ? null : { + type: 'SystemAssigned' +} + +resource search 'Microsoft.Search/searchServices@2021-04-01-preview' = { + name: name + location: location + tags: tags + // The free tier does not support managed identity + identity: searchIdentityProvider + properties: { + authOptions: authOptions + disableLocalAuth: disableLocalAuth + disabledDataExfiltrationOptions: disabledDataExfiltrationOptions + encryptionWithCmk: encryptionWithCmk + hostingMode: hostingMode + networkRuleSet: networkRuleSet + partitionCount: partitionCount + publicNetworkAccess: publicNetworkAccess + replicaCount: replicaCount + semanticSearch: semanticSearch + } + sku: sku +} + +output id string = search.id +output endpoint string = 'https://${name}.search.windows.net/' +output name string = search.name +output principalId string = !empty(searchIdentityProvider) ? search.identity.principalId : '' + diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/security/aks-managed-cluster-access.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/aks-managed-cluster-access.bicep new file mode 100644 index 00000000..dec984e8 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/aks-managed-cluster-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns RBAC role to the specified AKS cluster and principal.' +param clusterName string +param principalId string + +var aksClusterAdminRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b') + +resource aksRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aksCluster // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, aksClusterAdminRole) + properties: { + roleDefinitionId: aksClusterAdminRole + principalType: 'User' + principalId: principalId + } +} + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/security/keyvault-access.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/keyvault-access.bicep new file mode 100644 index 00000000..316775f2 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/keyvault-access.bicep @@ -0,0 +1,22 @@ +metadata description = 'Assigns an Azure Key Vault access policy.' +param name string = 'add' + +param keyVaultName string +param permissions object = { secrets: [ 'get', 'list' ] } +param principalId string + +resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = { + parent: keyVault + name: name + properties: { + accessPolicies: [ { + objectId: principalId + tenantId: subscription().tenantId + permissions: permissions + } ] + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/security/keyvault-secret.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/keyvault-secret.bicep new file mode 100644 index 00000000..7441b296 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/keyvault-secret.bicep @@ -0,0 +1,31 @@ +metadata description = 'Creates or updates a secret in an Azure Key Vault.' +param name string +param tags object = {} +param keyVaultName string +param contentType string = 'string' +@description('The value of the secret. Provide only derived values like blob storage access, but do not hard code any secrets in your templates') +@secure() +param secretValue string + +param enabled bool = true +param exp int = 0 +param nbf int = 0 + +resource keyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + name: name + tags: tags + parent: keyVault + properties: { + attributes: { + enabled: enabled + exp: exp + nbf: nbf + } + contentType: contentType + value: secretValue + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/security/keyvault.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/keyvault.bicep new file mode 100644 index 00000000..314a1db6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/keyvault.bicep @@ -0,0 +1,26 @@ +metadata description = 'Creates an Azure Key Vault.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param principalId string = '' + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: name + location: location + tags: tags + properties: { + tenantId: subscription().tenantId + sku: { family: 'A', name: 'standard' } + accessPolicies: !empty(principalId) ? [ + { + objectId: principalId + permissions: { secrets: [ 'get', 'list' ] } + tenantId: subscription().tenantId + } + ] : [] + } +} + +output endpoint string = keyVault.properties.vaultUri +output name string = keyVault.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/security/registry-access.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/registry-access.bicep new file mode 100644 index 00000000..5335efab --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/registry-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns ACR Pull permissions to access an Azure Container Registry.' +param containerRegistryName string +param principalId string + +var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + +resource aksAcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: containerRegistry // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, acrPullRole) + properties: { + roleDefinitionId: acrPullRole + principalType: 'ServicePrincipal' + principalId: principalId + } +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { + name: containerRegistryName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/security/role.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/role.bicep new file mode 100644 index 00000000..0b30cfd3 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/security/role.bicep @@ -0,0 +1,21 @@ +metadata description = 'Creates a role assignment for a service principal.' +param principalId string + +@allowed([ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' +]) +param principalType string = 'ServicePrincipal' +param roleDefinitionId string + +resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) + properties: { + principalId: principalId + principalType: principalType + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) + } +} diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/storage/storage-account.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/storage/storage-account.bicep new file mode 100644 index 00000000..4b6febbe --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/storage/storage-account.bicep @@ -0,0 +1,64 @@ +metadata description = 'Creates an Azure storage account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@allowed([ + 'Cool' + 'Hot' + 'Premium' ]) +param accessTier string = 'Hot' +param allowBlobPublicAccess bool = true +param allowCrossTenantReplication bool = true +param allowSharedKeyAccess bool = true +param containers array = [] +param defaultToOAuthAuthentication bool = false +param deleteRetentionPolicy object = {} +@allowed([ 'AzureDnsZone', 'Standard' ]) +param dnsEndpointType string = 'Standard' +param kind string = 'StorageV2' +param minimumTlsVersion string = 'TLS1_2' +param supportsHttpsTrafficOnly bool = true +param networkAcls object = { + bypass: 'AzureServices' + defaultAction: 'Allow' +} +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { name: 'Standard_LRS' } + +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { + name: name + location: location + tags: tags + kind: kind + sku: sku + properties: { + accessTier: accessTier + allowBlobPublicAccess: allowBlobPublicAccess + allowCrossTenantReplication: allowCrossTenantReplication + allowSharedKeyAccess: allowSharedKeyAccess + defaultToOAuthAuthentication: defaultToOAuthAuthentication + dnsEndpointType: dnsEndpointType + minimumTlsVersion: minimumTlsVersion + networkAcls: networkAcls + publicNetworkAccess: publicNetworkAccess + supportsHttpsTrafficOnly: supportsHttpsTrafficOnly + } + + resource blobServices 'blobServices' = if (!empty(containers)) { + name: 'default' + properties: { + deleteRetentionPolicy: deleteRetentionPolicy + } + resource container 'containers' = [for container in containers: { + name: container.name + properties: { + publicAccess: contains(container, 'publicAccess') ? container.publicAccess : 'None' + } + }] + } +} + +output name string = storage.name +output primaryEndpoints object = storage.properties.primaryEndpoints diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/core/testing/loadtesting.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/core/testing/loadtesting.bicep new file mode 100644 index 00000000..46781086 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/core/testing/loadtesting.bicep @@ -0,0 +1,15 @@ +param name string +param location string = resourceGroup().location +param managedIdentity bool = false +param tags object = {} + +resource loadTest 'Microsoft.LoadTestService/loadTests@2022-12-01' = { + name: name + location: location + tags: tags + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + properties: { + } +} + +output loadTestingName string = loadTest.name diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/main.bicep b/Environments/Contoso-Base-Shared-AKS-Dev/main.bicep new file mode 100644 index 00000000..c283ef31 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/main.bicep @@ -0,0 +1,55 @@ +@minLength(1) +@maxLength(64) +@description('Name of the the environment which is used to generate a short unique hash used in all resources.') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string = resourceGroup().location + +@description('The resource name of the AKS cluster') +param clusterName string = '' + +@description('The resource name of the Container Registry (ACR)') +param containerRegistryName string = '' + +param applicationInsightsDashboardName string = '' +param applicationInsightsName string = '' +param logAnalyticsName string = '' + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +// The AKS cluster to host applications +module aks './core/host/aks.bicep' = { + name: 'aks' + params: { + location: location + name: !empty(clusterName) ? clusterName : '${abbrs.containerServiceManagedClusters}${resourceToken}' + containerRegistryName: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + logAnalyticsName: monitoring.outputs.logAnalyticsWorkspaceName + } +} + +// Monitor application with Azure Monitor +module monitoring './core/monitor/monitoring.bicep' = { + name: 'monitoring' + params: { + location: location + tags: tags + logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}' + } +} + +// App outputs +output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId +output AZURE_AKS_CLUSTER_NAME string = aks.outputs.clusterName +output AZURE_AKS_IDENTITY_CLIENT_ID string = aks.outputs.clusterIdentity.clientId +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = aks.outputs.containerRegistryLoginServer +output AZURE_CONTAINER_REGISTRY_NAME string = aks.outputs.containerRegistryName +output REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/main.parameters.json b/Environments/Contoso-Base-Shared-AKS-Dev/main.parameters.json new file mode 100644 index 00000000..67ad8524 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/main.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "principalId": { + "value": "${AZURE_PRINCIPAL_ID}" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-AKS-Dev/manifest.yaml b/Environments/Contoso-Base-Shared-AKS-Dev/manifest.yaml new file mode 100644 index 00000000..2ea8e09d --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Dev/manifest.yaml @@ -0,0 +1,21 @@ +name: Contoso-Base-Shared-AKS-Dev +version: 1.0.0 +summary: Base shared environment for app development in AKS +description: Deploys base shared env (Container environment and monitoring) for app development in AKS +runner: ARM +templatePath: azuredeploy.json + +parameters: +- id: "environmentName" + name: "Environment Name (e.g. testenv)" + description: "Name of the Environment" + type: string + required: true + +- id: "location" + name: "Region (e.g. eastus)" + description: "Location of the resources" + type: string + required: true + + diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/abbreviations.json b/Environments/Contoso-Base-Shared-AKS-Prod/abbreviations.json new file mode 100644 index 00000000..289a08aa --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/abbreviations.json @@ -0,0 +1,136 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/app/db.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/app/db.bicep new file mode 100644 index 00000000..938949c6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/app/db.bicep @@ -0,0 +1,40 @@ +param accountName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [ + { + name: 'TodoList' + id: 'TodoList' + shardKey: 'Hash' + indexKey: '_id' + } + { + name: 'TodoItem' + id: 'TodoItem' + shardKey: 'Hash' + indexKey: '_id' + } +] +param databaseName string = '' +param keyVaultName string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module cosmos '../core/database/cosmos/mongo/cosmos-mongo-db.bicep' = { + name: 'cosmos-mongo' + params: { + accountName: accountName + databaseName: actualDatabaseName + location: location + collections: collections + keyVaultName: keyVaultName + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = cosmos.outputs.databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/azuredeploy.json b/Environments/Contoso-Base-Shared-AKS-Prod/azuredeploy.json new file mode 100644 index 00000000..0b774988 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/azuredeploy.json @@ -0,0 +1,2757 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "11426766062419716265" + } + }, + "parameters": { + "environmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the the environment which is used to generate a short unique hash used in all resources." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "minLength": 1, + "metadata": { + "description": "Primary location for all resources" + } + }, + "clusterName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource name of the AKS cluster" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource name of the Container Registry (ACR)" + } + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "" + } + }, + "variables": { + "$fxv#0": { + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" + }, + "abbrs": "[variables('$fxv#0')]", + "resourceToken": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "tags": { + "azd-env-name": "[parameters('environmentName')]" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aks", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "name": "[if(not(empty(parameters('clusterName'))), createObject('value', parameters('clusterName')), createObject('value', format('{0}{1}', variables('abbrs').containerServiceManagedClusters, variables('resourceToken'))))]", + "containerRegistryName": "[if(not(empty(parameters('containerRegistryName'))), createObject('value', parameters('containerRegistryName')), createObject('value', format('{0}{1}', variables('abbrs').containerRegistryRegistries, variables('resourceToken'))))]", + "logAnalyticsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.logAnalyticsWorkspaceName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "13278918148041449332" + }, + "description": "Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool as well as an additional user agent pool." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name for the AKS managed cluster" + } + }, + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The name for the Azure container registry (ACR)" + } + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the connected log analytics workspace" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The Azure region/location for the AKS resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom tags to apply to the AKS resources" + } + }, + "addOns": { + "type": "object", + "defaultValue": { + "azurePolicy": { + "enabled": true, + "config": { + "version": "v2" + } + }, + "keyVault": { + "enabled": true, + "config": { + "enableSecretRotation": "true", + "rotationPollInterval": "2m" + } + }, + "openServiceMesh": { + "enabled": false, + "config": {} + }, + "omsAgent": { + "enabled": true, + "config": {} + }, + "applicationGateway": { + "enabled": false, + "config": {} + } + }, + "metadata": { + "description": "AKS add-ons configuration" + } + }, + "sku": { + "type": "string", + "defaultValue": "Free", + "allowedValues": [ + "Free", + "Paid", + "Standard" + ], + "metadata": { + "description": "The managed cluster SKU." + } + }, + "loadBalancerSku": { + "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "basic", + "standard" + ], + "metadata": { + "description": "The load balancer SKU to use for ingress into the AKS cluster" + } + }, + "networkPlugin": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "kubenet", + "none" + ], + "metadata": { + "description": "Network plugin used for building the Kubernetes network." + } + }, + "networkPolicy": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "calico" + ], + "metadata": { + "description": "Network policy used for building the Kubernetes network." + } + }, + "dnsPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The DNS prefix to associate with the AKS cluster" + } + }, + "nodeResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the resource group for the managed resources of the AKS cluster" + } + }, + "systemPoolType": { + "type": "string", + "defaultValue": "CostOptimised", + "allowedValues": [ + "CostOptimised", + "Standard", + "HighSpec", + "Custom" + ], + "metadata": { + "description": "The System Pool Preset sizing" + } + }, + "agentPoolType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "CostOptimised", + "Standard", + "HighSpec", + "Custom" + ], + "metadata": { + "description": "The User Pool Preset sizing" + } + }, + "systemPoolConfig": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom configuration of system node pool" + } + }, + "agentPoolConfig": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom configuration of user node pool" + } + }, + "principalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Id of the user or app to assign application roles" + } + }, + "kubernetesVersion": { + "type": "string", + "defaultValue": "1.27.7", + "metadata": { + "description": "Kubernetes Version" + } + }, + "aadTenantId": { + "type": "string", + "defaultValue": "[tenant().tenantId]", + "metadata": { + "description": "The Tenant ID associated to the Azure Active Directory" + } + }, + "enableRbac": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether RBAC is enabled for local accounts" + } + }, + "disableLocalAccounts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "If set to true, getting static credentials will be disabled for this cluster." + } + }, + "enableAzureRbac": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable RBAC using AAD" + } + }, + "webAppRoutingAddon": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether web app routing (preview) add-on is enabled" + } + } + }, + "variables": { + "omsAgentConfig": "[if(and(and(not(empty(parameters('logAnalyticsName'))), not(empty(parameters('addOns').omsAgent))), parameters('addOns').omsAgent.enabled), union(parameters('addOns').omsAgent, createObject('config', createObject('logAnalyticsWorkspaceResourceID', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))))), createObject())]", + "addOnsConfig": "[union(if(and(not(empty(parameters('addOns').azurePolicy)), parameters('addOns').azurePolicy.enabled), createObject('azurepolicy', parameters('addOns').azurePolicy), createObject()), if(and(not(empty(parameters('addOns').keyVault)), parameters('addOns').keyVault.enabled), createObject('azureKeyvaultSecretsProvider', parameters('addOns').keyVault), createObject()), if(and(not(empty(parameters('addOns').openServiceMesh)), parameters('addOns').openServiceMesh.enabled), createObject('openServiceMesh', parameters('addOns').openServiceMesh), createObject()), if(and(not(empty(parameters('addOns').omsAgent)), parameters('addOns').omsAgent.enabled), createObject('omsagent', variables('omsAgentConfig')), createObject()), if(and(not(empty(parameters('addOns').applicationGateway)), parameters('addOns').applicationGateway.enabled), createObject('ingressApplicationGateway', parameters('addOns').applicationGateway), createObject()))]", + "systemPoolSpec": "[if(not(empty(parameters('systemPoolConfig'))), parameters('systemPoolConfig'), variables('nodePoolPresets')[parameters('systemPoolType')])]", + "hasAgentPool": "[or(not(empty(parameters('agentPoolConfig'))), not(empty(parameters('agentPoolType'))))]", + "agentPoolSpec": "[if(and(variables('hasAgentPool'), not(empty(parameters('agentPoolConfig')))), parameters('agentPoolConfig'), if(empty(parameters('agentPoolType')), createObject(), variables('nodePoolPresets')[parameters('agentPoolType')]))]", + "nodePoolBase": { + "osType": "Linux", + "maxPods": 30, + "type": "VirtualMachineScaleSets", + "upgradeSettings": { + "maxSurge": "33%" + } + }, + "nodePoolPresets": { + "CostOptimised": { + "vmSize": "Standard_B4ms", + "count": 1, + "minCount": 1, + "maxCount": 3, + "enableAutoScaling": true, + "availabilityZones": [] + }, + "Standard": { + "vmSize": "Standard_DS2_v2", + "count": 3, + "minCount": 3, + "maxCount": 5, + "enableAutoScaling": true, + "availabilityZones": [ + "1", + "2", + "3" + ] + }, + "HighSpec": { + "vmSize": "Standard_D4s_v3", + "count": 3, + "minCount": 3, + "maxCount": 5, + "enableAutoScaling": true, + "availabilityZones": [ + "1", + "2", + "3" + ] + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "managed-cluster", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "systemPoolConfig": { + "value": "[union(createObject('name', 'npsystem', 'mode', 'System'), variables('nodePoolBase'), variables('systemPoolSpec'))]" + }, + "nodeResourceGroupName": { + "value": "[parameters('nodeResourceGroupName')]" + }, + "sku": { + "value": "[parameters('sku')]" + }, + "dnsPrefix": { + "value": "[parameters('dnsPrefix')]" + }, + "kubernetesVersion": { + "value": "[parameters('kubernetesVersion')]" + }, + "addOns": { + "value": "[variables('addOnsConfig')]" + }, + "workspaceId": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))), createObject('value', ''))]", + "enableAad": { + "value": "[and(parameters('enableAzureRbac'), not(equals(parameters('aadTenantId'), '')))]" + }, + "disableLocalAccounts": { + "value": "[parameters('disableLocalAccounts')]" + }, + "aadTenantId": { + "value": "[parameters('aadTenantId')]" + }, + "enableRbac": { + "value": "[parameters('enableRbac')]" + }, + "enableAzureRbac": { + "value": "[parameters('enableAzureRbac')]" + }, + "webAppRoutingAddon": { + "value": "[parameters('webAppRoutingAddon')]" + }, + "loadBalancerSku": { + "value": "[parameters('loadBalancerSku')]" + }, + "networkPlugin": { + "value": "[parameters('networkPlugin')]" + }, + "networkPolicy": { + "value": "[parameters('networkPolicy')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8184151159222198677" + }, + "description": "Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name for the AKS managed cluster" + } + }, + "nodeResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the resource group for the managed resources of the AKS cluster" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The Azure region/location for the AKS resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom tags to apply to the AKS resources" + } + }, + "kubernetesVersion": { + "type": "string", + "defaultValue": "1.27.7", + "metadata": { + "description": "Kubernetes Version" + } + }, + "enableRbac": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether RBAC is enabled for local accounts" + } + }, + "webAppRoutingAddon": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether web app routing (preview) add-on is enabled" + } + }, + "enableAad": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable Azure Active Directory integration" + } + }, + "enableAzureRbac": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable RBAC using AAD" + } + }, + "aadTenantId": { + "type": "string", + "defaultValue": "[tenant().tenantId]", + "metadata": { + "description": "The Tenant ID associated to the Azure Active Directory" + } + }, + "loadBalancerSku": { + "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "basic", + "standard" + ], + "metadata": { + "description": "The load balancer SKU to use for ingress into the AKS cluster" + } + }, + "networkPlugin": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "kubenet", + "none" + ], + "metadata": { + "description": "Network plugin used for building the Kubernetes network." + } + }, + "networkPolicy": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "calico" + ], + "metadata": { + "description": "Network policy used for building the Kubernetes network." + } + }, + "disableLocalAccounts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "If set to true, getting static credentials will be disabled for this cluster." + } + }, + "sku": { + "type": "string", + "defaultValue": "Free", + "allowedValues": [ + "Free", + "Paid", + "Standard" + ], + "metadata": { + "description": "The managed cluster SKU." + } + }, + "addOns": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Configuration of AKS add-ons" + } + }, + "workspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The log analytics workspace id used for logging & monitoring" + } + }, + "systemPoolConfig": { + "type": "object", + "metadata": { + "description": "The node pool configuration for the System agent pool" + } + }, + "dnsPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The DNS prefix to associate with the AKS cluster" + } + } + }, + "variables": { + "aksDiagCategories": [ + "cluster-autoscaler", + "kube-controller-manager", + "kube-audit-admin", + "guard" + ] + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters", + "apiVersion": "2023-10-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "sku": { + "name": "Base", + "tier": "[parameters('sku')]" + }, + "properties": { + "nodeResourceGroup": "[if(not(empty(parameters('nodeResourceGroupName'))), parameters('nodeResourceGroupName'), format('rg-mc-{0}', parameters('name')))]", + "kubernetesVersion": "[parameters('kubernetesVersion')]", + "dnsPrefix": "[if(empty(parameters('dnsPrefix')), format('{0}-dns', parameters('name')), parameters('dnsPrefix'))]", + "enableRBAC": "[parameters('enableRbac')]", + "aadProfile": "[if(parameters('enableAad'), createObject('managed', true(), 'enableAzureRBAC', parameters('enableAzureRbac'), 'tenantID', parameters('aadTenantId')), null())]", + "agentPoolProfiles": [ + "[parameters('systemPoolConfig')]" + ], + "networkProfile": { + "loadBalancerSku": "[parameters('loadBalancerSku')]", + "networkPlugin": "[parameters('networkPlugin')]", + "networkPolicy": "[parameters('networkPolicy')]" + }, + "disableLocalAccounts": "[and(parameters('disableLocalAccounts'), parameters('enableAad'))]", + "addonProfiles": "[parameters('addOns')]", + "ingressProfile": { + "webAppRouting": { + "enabled": "[parameters('webAppRoutingAddon')]" + } + } + } + }, + { + "condition": "[not(empty(parameters('workspaceId')))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerService/managedClusters/{0}', parameters('name'))]", + "name": "aks-diagnostics", + "properties": { + "copy": [ + { + "name": "logs", + "count": "[length(variables('aksDiagCategories'))]", + "input": { + "category": "[variables('aksDiagCategories')[copyIndex('logs')]]", + "enabled": true + } + } + ], + "workspaceId": "[parameters('workspaceId')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerService/managedClusters', parameters('name'))]" + ] + } + ], + "outputs": { + "clusterName": { + "type": "string", + "metadata": { + "description": "The resource name of the AKS cluster" + }, + "value": "[parameters('name')]" + }, + "clusterIdentity": { + "type": "object", + "metadata": { + "description": "The AKS cluster identity" + }, + "value": { + "clientId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.clientId]", + "objectId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.objectId]", + "resourceId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.resourceId]" + } + } + } + } + } + }, + { + "condition": "[variables('hasAgentPool')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aks-node-pool", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "name": { + "value": "npuserpool" + }, + "config": { + "value": "[union(createObject('name', 'npuser', 'mode', 'User'), variables('nodePoolBase'), variables('agentPoolSpec'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "6072270897511874144" + }, + "description": "Adds an agent pool to an Azure Kubernetes Service (AKS) cluster." + }, + "parameters": { + "clusterName": { + "type": "string" + }, + "name": { + "type": "string", + "metadata": { + "description": "The agent pool name" + } + }, + "config": { + "type": "object", + "metadata": { + "description": "The agent pool configuration" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters/agentPools", + "apiVersion": "2023-10-02-preview", + "name": "[format('{0}/{1}', parameters('clusterName'), parameters('name'))]", + "properties": "[parameters('config')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-registry", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerRegistryName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "workspaceId": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "12834334744516280883" + }, + "description": "Creates an Azure Container Registry." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "adminUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether admin user is enabled" + } + }, + "anonymousPullEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether anonymous pull is enabled" + } + }, + "dataEndpointEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether data endpoint is enabled" + } + }, + "encryption": { + "type": "object", + "defaultValue": { + "status": "disabled" + }, + "metadata": { + "description": "Encryption settings" + } + }, + "networkRuleBypassOptions": { + "type": "string", + "defaultValue": "AzureServices", + "metadata": { + "description": "Options for bypassing network rules" + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Public network access setting" + } + }, + "sku": { + "type": "object", + "defaultValue": { + "name": "Basic" + }, + "metadata": { + "description": "SKU settings" + } + }, + "zoneRedundancy": { + "type": "string", + "defaultValue": "Disabled", + "metadata": { + "description": "Zone redundancy setting" + } + }, + "workspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The log analytics workspace ID used for logging and monitoring" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2022-02-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": "[parameters('sku')]", + "properties": { + "adminUserEnabled": "[parameters('adminUserEnabled')]", + "anonymousPullEnabled": "[parameters('anonymousPullEnabled')]", + "dataEndpointEnabled": "[parameters('dataEndpointEnabled')]", + "encryption": "[parameters('encryption')]", + "networkRuleBypassOptions": "[parameters('networkRuleBypassOptions')]", + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "zoneRedundancy": "[parameters('zoneRedundancy')]" + } + }, + { + "condition": "[not(empty(parameters('workspaceId')))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "registry-diagnostics", + "properties": { + "workspaceId": "[parameters('workspaceId')]", + "logs": [ + { + "category": "ContainerRegistryRepositoryEvents", + "enabled": true + }, + { + "category": "ContainerRegistryLoginEvents", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true, + "timeGrain": "PT1M" + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" + ] + } + ], + "outputs": { + "loginServer": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), '2022-02-01-preview').loginServer]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cluster-container-registry-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerRegistryName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.name.value]" + }, + "principalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterIdentity.value.objectId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "15144906240959446537" + }, + "description": "Assigns ACR Pull permissions to access an Azure Container Registry." + }, + "parameters": { + "containerRegistryName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "acrPullRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('containerRegistryName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('acrPullRole'))]", + "properties": { + "roleDefinitionId": "[variables('acrPullRole')]", + "principalType": "ServicePrincipal", + "principalId": "[parameters('principalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'container-registry')]", + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + }, + { + "condition": "[or(parameters('enableAzureRbac'), parameters('disableLocalAccounts'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cluster-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "principalId": { + "value": "[parameters('principalId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8205013527918052324" + }, + "description": "Assigns RBAC role to the specified AKS cluster and principal." + }, + "parameters": { + "clusterName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "aksClusterAdminRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerService/managedClusters/{0}', parameters('clusterName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('aksClusterAdminRole'))]", + "properties": { + "roleDefinitionId": "[variables('aksClusterAdminRole')]", + "principalType": "User", + "principalId": "[parameters('principalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + } + ], + "outputs": { + "clusterName": { + "type": "string", + "metadata": { + "description": "The resource name of the AKS cluster" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "clusterIdentity": { + "type": "object", + "metadata": { + "description": "The AKS cluster identity" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterIdentity.value]" + }, + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The resource name of the ACR" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.name.value]" + }, + "containerRegistryLoginServer": { + "type": "string", + "metadata": { + "description": "The login server for the container registry" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.loginServer.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "monitoring", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "logAnalyticsName": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', parameters('logAnalyticsName')), createObject('value', format('{0}{1}', variables('abbrs').operationalInsightsWorkspaces, variables('resourceToken'))))]", + "applicationInsightsName": "[if(not(empty(parameters('applicationInsightsName'))), createObject('value', parameters('applicationInsightsName')), createObject('value', format('{0}{1}', variables('abbrs').insightsComponents, variables('resourceToken'))))]", + "applicationInsightsDashboardName": "[if(not(empty(parameters('applicationInsightsDashboardName'))), createObject('value', parameters('applicationInsightsDashboardName')), createObject('value', format('{0}{1}', variables('abbrs').portalDashboards, variables('resourceToken'))))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10041669792322197047" + }, + "description": "Creates an Application Insights instance and a Log Analytics workspace." + }, + "parameters": { + "logAnalyticsName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "loganalytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('logAnalyticsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "9622176141085970536" + }, + "description": "Creates a Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-12-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": 30, + "features": { + "searchVersion": 1 + }, + "sku": { + "name": "PerGB2018" + } + } + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "applicationinsights", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('applicationInsightsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "dashboardName": { + "value": "[parameters('applicationInsightsDashboardName')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1335628967363670282" + }, + "description": "Creates an Application Insights instance based on an existing Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "dashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "logAnalyticsWorkspaceId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]" + } + }, + { + "condition": "[not(empty(parameters('dashboardName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "application-insights-dashboard", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('dashboardName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "applicationInsightsName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "2145880658446193205" + }, + "description": "Creates a dashboard for an Application Insights instance." + }, + "parameters": { + "name": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Portal/dashboards", + "apiVersion": "2020-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "lenses": [ + { + "order": 0, + "parts": [ + { + "position": { + "x": 0, + "y": 0, + "colSpan": 2, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "id", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart", + "asset": { + "idInputName": "id", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "overview" + } + }, + { + "position": { + "x": 2, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "ProactiveDetection" + } + }, + { + "position": { + "x": 3, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + } + ], + "type": "Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:20:33.345Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 5, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-08T18:47:35.237Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "ConfigurationId", + "value": "78ce933e-e864-4b05-a27b-71fd55a6afad" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AppMapButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 0, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Usage", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 3, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:22:35.782Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + } + ], + "type": "Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Reliability", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 7, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:42:40.072Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "failures" + } + }, + { + "position": { + "x": 8, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Responsiveness\r\n", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 11, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:43:37.804Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "2a8ede4f-2bee-4b9c-aed9-2db0e8a01865", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "performance" + } + }, + { + "position": { + "x": 12, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Browser", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 15, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "MetricsExplorerJsonDefinitionId", + "value": "BrowserPerformanceTimelineMetrics" + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "createdTime": "2018-05-08T12:16:27.534Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "CurrentFilter", + "value": { + "eventTypes": [ + 4, + 1, + 3, + 5, + 2, + 6, + 13 + ], + "typeFacets": {}, + "isPermissive": false + } + }, + { + "name": "id", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "browser" + } + }, + { + "position": { + "x": 0, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "sessions/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Sessions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "users/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Users", + "color": "#7E58FF" + } + } + ], + "title": "Unique sessions and users", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "segmentationUsers" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Failed requests", + "color": "#EC008C" + } + } + ], + "title": "Failed requests", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "failures" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/duration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server response time", + "color": "#00BCF2" + } + } + ], + "title": "Server response time", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "performance" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/networkDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Page load network connect time", + "color": "#7E58FF" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/processingDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Client processing time", + "color": "#44F1C8" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/sendDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Send request time", + "color": "#EB9371" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/receiveDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Receiving response time", + "color": "#0672F1" + } + } + ], + "title": "Average page load time breakdown", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/availabilityPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability", + "color": "#47BDF5" + } + } + ], + "title": "Average availability", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "availability" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/server", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server exceptions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "dependencies/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Dependency failures", + "color": "#7E58FF" + } + } + ], + "title": "Server exceptions and Dependency failures", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processorCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Processor time", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process CPU", + "color": "#7E58FF" + } + } + ], + "title": "Average processor and process CPU utilization", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/browser", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Browser exceptions", + "color": "#47BDF5" + } + } + ], + "title": "Browser exceptions", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/count", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability test results count", + "color": "#47BDF5" + } + } + ], + "title": "Availability test results count", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processIOBytesPerSecond", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process IO rate", + "color": "#47BDF5" + } + } + ], + "title": "Average process I/O rate", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/memoryAvailableBytes", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Available memory", + "color": "#47BDF5" + } + } + ], + "title": "Average available memory", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + } + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').ConnectionString]" + }, + "instrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').InstrumentationKey]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'loganalytics')]" + ] + } + ], + "outputs": { + "applicationInsightsConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.connectionString.value]" + }, + "applicationInsightsInstrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "applicationInsightsName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.name.value]" + } + } + } + } + } + ], + "outputs": { + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "AZURE_LOCATION": { + "type": "string", + "value": "[parameters('location')]" + }, + "AZURE_TENANT_ID": { + "type": "string", + "value": "[tenant().tenantId]" + }, + "AZURE_AKS_CLUSTER_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.clusterName.value]" + }, + "AZURE_AKS_IDENTITY_CLIENT_ID": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.clusterIdentity.value.clientId]" + }, + "AZURE_CONTAINER_REGISTRY_ENDPOINT": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.containerRegistryLoginServer.value]" + }, + "AZURE_CONTAINER_REGISTRY_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.containerRegistryName.value]" + }, + "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/ai/cognitiveservices.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/ai/cognitiveservices.bicep new file mode 100644 index 00000000..1bf5666b --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/ai/cognitiveservices.bicep @@ -0,0 +1,53 @@ +metadata description = 'Creates an Azure Cognitive Services instance.' +param name string +param location string = resourceGroup().location +param tags object = {} +@description('The custom subdomain name used to access the API. Defaults to the value of the name parameter.') +param customSubDomainName string = name +param deployments array = [] +param kind string = 'OpenAI' + +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { + name: 'S0' +} + +param allowedIpRules array = [] +param networkAcls object = empty(allowedIpRules) ? { + defaultAction: 'Allow' +} : { + ipRules: allowedIpRules + defaultAction: 'Deny' +} + +resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + customSubDomainName: customSubDomainName + publicNetworkAccess: publicNetworkAccess + networkAcls: networkAcls + } + sku: sku +} + +@batchSize(1) +resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: { + parent: account + name: deployment.name + properties: { + model: deployment.model + raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null + } + sku: contains(deployment, 'sku') ? deployment.sku : { + name: 'Standard' + capacity: 20 + } +}] + +output endpoint string = account.properties.endpoint +output id string = account.id +output name string = account.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/cosmos-account.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/cosmos-account.bicep new file mode 100644 index 00000000..6f8747f5 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/cosmos-account.bicep @@ -0,0 +1,49 @@ +metadata description = 'Creates an Azure Cosmos DB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +@allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) +param kind string + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { + name: name + kind: kind + location: location + tags: tags + properties: { + consistencyPolicy: { defaultConsistencyLevel: 'Session' } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + databaseAccountOfferType: 'Standard' + enableAutomaticFailover: false + enableMultipleWriteLocations: false + apiProperties: (kind == 'MongoDB') ? { serverVersion: '4.2' } : {} + capabilities: [ { name: 'EnableServerless' } ] + } +} + +resource cosmosConnectionString 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: cosmos.listConnectionStrings().connectionStrings[0].connectionString + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output connectionStringKey string = connectionStringKey +output endpoint string = cosmos.properties.documentEndpoint +output id string = cosmos.id +output name string = cosmos.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/mongo/cosmos-mongo-account.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/mongo/cosmos-mongo-account.bicep new file mode 100644 index 00000000..4aafbf38 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/mongo/cosmos-mongo-account.bicep @@ -0,0 +1,23 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + connectionStringKey: connectionStringKey + keyVaultName: keyVaultName + kind: 'MongoDB' + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/mongo/cosmos-mongo-db.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/mongo/cosmos-mongo-db.bicep new file mode 100644 index 00000000..2a670578 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/mongo/cosmos-mongo-db.bicep @@ -0,0 +1,47 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [] +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +module cosmos 'cosmos-mongo-account.bicep' = { + name: 'cosmos-mongo-account' + params: { + name: accountName + location: location + keyVaultName: keyVaultName + tags: tags + connectionStringKey: connectionStringKey + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2022-08-15' = { + name: '${accountName}/${databaseName}' + tags: tags + properties: { + resource: { id: databaseName } + } + + resource list 'collections' = [for collection in collections: { + name: collection.name + properties: { + resource: { + id: collection.id + shardKey: { _id: collection.shardKey } + indexes: [ { key: { keys: [ collection.indexKey ] } } ] + } + } + }] + + dependsOn: [ + cosmos + ] +} + +output connectionStringKey string = connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-account.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-account.bicep new file mode 100644 index 00000000..8431135e --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-account.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + tags: tags + keyVaultName: keyVaultName + kind: 'GlobalDocumentDB' + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id +output name string = cosmos.outputs.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-db.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-db.bicep new file mode 100644 index 00000000..265880dc --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-db.bicep @@ -0,0 +1,74 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param containers array = [] +param keyVaultName string +param principalIds array = [] + +module cosmos 'cosmos-sql-account.bicep' = { + name: 'cosmos-sql-account' + params: { + name: accountName + location: location + tags: tags + keyVaultName: keyVaultName + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = { + name: '${accountName}/${databaseName}' + properties: { + resource: { id: databaseName } + } + + resource list 'containers' = [for container in containers: { + name: container.name + properties: { + resource: { + id: container.id + partitionKey: { paths: [ container.partitionKey ] } + } + options: {} + } + }] + + dependsOn: [ + cosmos + ] +} + +module roleDefinition 'cosmos-sql-role-def.bicep' = { + name: 'cosmos-sql-role-definition' + params: { + accountName: accountName + } + dependsOn: [ + cosmos + database + ] +} + +// We need batchSize(1) here because sql role assignments have to be done sequentially +@batchSize(1) +module userRole 'cosmos-sql-role-assign.bicep' = [for principalId in principalIds: if (!empty(principalId)) { + name: 'cosmos-sql-user-role-${uniqueString(principalId)}' + params: { + accountName: accountName + roleDefinitionId: roleDefinition.outputs.id + principalId: principalId + } + dependsOn: [ + cosmos + database + ] +}] + +output accountId string = cosmos.outputs.id +output accountName string = cosmos.outputs.name +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint +output roleDefinitionId string = roleDefinition.outputs.id diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-role-assign.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-role-assign.bicep new file mode 100644 index 00000000..3949efef --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-role-assign.bicep @@ -0,0 +1,19 @@ +metadata description = 'Creates a SQL role assignment under an Azure Cosmos DB account.' +param accountName string + +param roleDefinitionId string +param principalId string = '' + +resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { + parent: cosmos + name: guid(roleDefinitionId, principalId, cosmos.id) + properties: { + principalId: principalId + roleDefinitionId: roleDefinitionId + scope: cosmos.id + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-role-def.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-role-def.bicep new file mode 100644 index 00000000..778d6dc4 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/cosmos/sql/cosmos-sql-role-def.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates a SQL role definition under an Azure Cosmos DB account.' +param accountName string + +resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2022-08-15' = { + parent: cosmos + name: guid(cosmos.id, accountName, 'sql-role') + properties: { + assignableScopes: [ + cosmos.id + ] + permissions: [ + { + dataActions: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + ] + notDataActions: [] + } + ] + roleName: 'Reader Writer' + type: 'CustomRole' + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} + +output id string = roleDefinition.id diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/database/postgresql/flexibleserver.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/postgresql/flexibleserver.bicep new file mode 100644 index 00000000..7e26b1a8 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/postgresql/flexibleserver.bicep @@ -0,0 +1,65 @@ +metadata description = 'Creates an Azure Database for PostgreSQL - Flexible Server.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object +param storage object +param administratorLogin string +@secure() +param administratorLoginPassword string +param databaseNames array = [] +param allowAzureIPsFirewall bool = false +param allowAllIPsFirewall bool = false +param allowedSingleIPs array = [] + +// PostgreSQL version +param version string + +// Latest official version 2022-12-01 does not have Bicep types available +resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = { + location: location + tags: tags + name: name + sku: sku + properties: { + version: version + administratorLogin: administratorLogin + administratorLoginPassword: administratorLoginPassword + storage: storage + highAvailability: { + mode: 'Disabled' + } + } + + resource database 'databases' = [for name in databaseNames: { + name: name + }] + + resource firewall_all 'firewallRules' = if (allowAllIPsFirewall) { + name: 'allow-all-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '255.255.255.255' + } + } + + resource firewall_azure 'firewallRules' = if (allowAzureIPsFirewall) { + name: 'allow-all-azure-internal-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '0.0.0.0' + } + } + + resource firewall_single 'firewallRules' = [for ip in allowedSingleIPs: { + name: 'allow-single-${replace(ip, '.', '')}' + properties: { + startIpAddress: ip + endIpAddress: ip + } + }] + +} + +output POSTGRES_DOMAIN_NAME string = postgresServer.properties.fullyQualifiedDomainName diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/database/sqlserver/sqlserver.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/sqlserver/sqlserver.bicep new file mode 100644 index 00000000..84f2cc2c --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/database/sqlserver/sqlserver.bicep @@ -0,0 +1,130 @@ +metadata description = 'Creates an Azure SQL Server instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param appUser string = 'appUser' +param databaseName string +param keyVaultName string +param sqlAdmin string = 'sqlAdmin' +param connectionStringKey string = 'AZURE-SQL-CONNECTION-STRING' + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name + location: location + tags: tags + properties: { + version: '12.0' + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + administratorLogin: sqlAdmin + administratorLoginPassword: sqlAdminPassword + } + + resource database 'databases' = { + name: databaseName + location: location + } + + resource firewall 'firewallRules' = { + name: 'Azure Services' + properties: { + // Allow all clients + // Note: range [0.0.0.0-0.0.0.0] means "allow all Azure-hosted clients only". + // This is not sufficient, because we also want to allow direct access from developer machine, for debugging purposes. + startIpAddress: '0.0.0.1' + endIpAddress: '255.255.255.254' + } + } +} + +resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: '${name}-deployment-script' + location: location + kind: 'AzureCLI' + properties: { + azCliVersion: '2.37.0' + retentionInterval: 'PT1H' // Retain the script resource for 1 hour after it ends running + timeout: 'PT5M' // Five minutes + cleanupPreference: 'OnSuccess' + environmentVariables: [ + { + name: 'APPUSERNAME' + value: appUser + } + { + name: 'APPUSERPASSWORD' + secureValue: appUserPassword + } + { + name: 'DBNAME' + value: databaseName + } + { + name: 'DBSERVER' + value: sqlServer.properties.fullyQualifiedDomainName + } + { + name: 'SQLCMDPASSWORD' + secureValue: sqlAdminPassword + } + { + name: 'SQLADMIN' + value: sqlAdmin + } + ] + + scriptContent: ''' +wget https://github.com/microsoft/go-sqlcmd/releases/download/v0.8.1/sqlcmd-v0.8.1-linux-x64.tar.bz2 +tar x -f sqlcmd-v0.8.1-linux-x64.tar.bz2 -C . + +cat < ./initDb.sql +drop user if exists ${APPUSERNAME} +go +create user ${APPUSERNAME} with password = '${APPUSERPASSWORD}' +go +alter role db_owner add member ${APPUSERNAME} +go +SCRIPT_END + +./sqlcmd -S ${DBSERVER} -d ${DBNAME} -U ${SQLADMIN} -i ./initDb.sql + ''' + } +} + +resource sqlAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'sqlAdminPassword' + properties: { + value: sqlAdminPassword + } +} + +resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'appUserPassword' + properties: { + value: appUserPassword + } +} + +resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: '${connectionString}; Password=${appUserPassword}' + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output connectionStringKey string = connectionStringKey +output databaseName string = sqlServer::database.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/gateway/apim.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/gateway/apim.bicep new file mode 100644 index 00000000..be7464f0 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/gateway/apim.bicep @@ -0,0 +1,79 @@ +metadata description = 'Creates an Azure API Management instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The email address of the owner of the service') +@minLength(1) +param publisherEmail string = 'noreply@microsoft.com' + +@description('The name of the owner of the service') +@minLength(1) +param publisherName string = 'n/a' + +@description('The pricing tier of this API Management service') +@allowed([ + 'Consumption' + 'Developer' + 'Standard' + 'Premium' +]) +param sku string = 'Consumption' + +@description('The instance size of this API Management service.') +@allowed([ 0, 1, 2 ]) +param skuCount int = 0 + +@description('Azure Application Insights Name') +param applicationInsightsName string + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': name }) + sku: { + name: sku + capacity: (sku == 'Consumption') ? 0 : ((sku == 'Developer') ? 1 : skuCount) + } + properties: { + publisherEmail: publisherEmail + publisherName: publisherName + // Custom properties are not supported for Consumption SKU + customProperties: sku == 'Consumption' ? {} : { + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_GCM_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TripleDes168': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Ssl30': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30': 'false' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' = if (!empty(applicationInsightsName)) { + name: 'app-insights-logger' + parent: apimService + properties: { + credentials: { + instrumentationKey: applicationInsights.properties.InstrumentationKey + } + description: 'Logger to Azure Application Insights' + isBuffered: false + loggerType: 'applicationInsights' + resourceId: applicationInsights.id + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output apimServiceName string = apimService.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/aks-agent-pool.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/aks-agent-pool.bicep new file mode 100644 index 00000000..9c764358 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/aks-agent-pool.bicep @@ -0,0 +1,18 @@ +metadata description = 'Adds an agent pool to an Azure Kubernetes Service (AKS) cluster.' +param clusterName string + +@description('The agent pool name') +param name string + +@description('The agent pool configuration') +param config object + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} + +resource nodePool 'Microsoft.ContainerService/managedClusters/agentPools@2023-10-02-preview' = { + parent: aksCluster + name: name + properties: config +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/aks-managed-cluster.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/aks-managed-cluster.bicep new file mode 100644 index 00000000..de562a66 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/aks-managed-cluster.bicep @@ -0,0 +1,140 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// AAD Integration +@description('Enable Azure Active Directory integration') +param enableAad bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('Configuration of AKS add-ons') +param addOns object = {} + +@description('The log analytics workspace id used for logging & monitoring') +param workspaceId string = '' + +@description('The node pool configuration for the System agent pool') +param systemPoolConfig object + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +resource aks 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' = { + name: name + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + sku: { + name: 'Base' + tier: sku + } + properties: { + nodeResourceGroup: !empty(nodeResourceGroupName) ? nodeResourceGroupName : 'rg-mc-${name}' + kubernetesVersion: kubernetesVersion + dnsPrefix: empty(dnsPrefix) ? '${name}-dns' : dnsPrefix + enableRBAC: enableRbac + aadProfile: enableAad ? { + managed: true + enableAzureRBAC: enableAzureRbac + tenantID: aadTenantId + } : null + agentPoolProfiles: [ + systemPoolConfig + ] + networkProfile: { + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } + disableLocalAccounts: disableLocalAccounts && enableAad + addonProfiles: addOns + ingressProfile: { + webAppRouting: { + enabled: webAppRoutingAddon + } + } + } +} + +var aksDiagCategories = [ + 'cluster-autoscaler' + 'kube-controller-manager' + 'kube-audit-admin' + 'guard' +] + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'aks-diagnostics' + scope: aks + properties: { + workspaceId: workspaceId + logs: [for category in aksDiagCategories: { + category: category + enabled: true + }] + metrics: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +} + +@description('The resource name of the AKS cluster') +output clusterName string = aks.name + +@description('The AKS cluster identity') +output clusterIdentity object = { + clientId: aks.properties.identityProfile.kubeletidentity.clientId + objectId: aks.properties.identityProfile.kubeletidentity.objectId + resourceId: aks.properties.identityProfile.kubeletidentity.resourceId +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/aks.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/aks.bicep new file mode 100644 index 00000000..4a36262c --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/aks.bicep @@ -0,0 +1,268 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool as well as an additional user agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name for the Azure container registry (ACR)') +param containerRegistryName string + +@description('The name of the connected log analytics workspace') +param logAnalyticsName string = '' + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('AKS add-ons configuration') +param addOns object = { + azurePolicy: { + enabled: true + config: { + version: 'v2' + } + } + keyVault: { + enabled: true + config: { + enableSecretRotation: 'true' + rotationPollInterval: '2m' + } + } + openServiceMesh: { + enabled: false + config: {} + } + omsAgent: { + enabled: true + config: {} + } + applicationGateway: { + enabled: false + config: {} + } +} + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@allowed([ + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The System Pool Preset sizing') +param systemPoolType string = 'CostOptimised' + +@allowed([ + '' + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The User Pool Preset sizing') +param agentPoolType string = '' + +// Configure system / user agent pools +@description('Custom configuration of system node pool') +param systemPoolConfig object = {} +@description('Custom configuration of user node pool') +param agentPoolConfig object = {} + +@description('Id of the user or app to assign application roles') +param principalId string = '' + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// Configure AKS add-ons +var omsAgentConfig = (!empty(logAnalyticsName) && !empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? union( + addOns.omsAgent, + { + config: { + logAnalyticsWorkspaceResourceID: logAnalytics.id + } + } +) : {} + +var addOnsConfig = union( + (!empty(addOns.azurePolicy) && addOns.azurePolicy.enabled) ? { azurepolicy: addOns.azurePolicy } : {}, + (!empty(addOns.keyVault) && addOns.keyVault.enabled) ? { azureKeyvaultSecretsProvider: addOns.keyVault } : {}, + (!empty(addOns.openServiceMesh) && addOns.openServiceMesh.enabled) ? { openServiceMesh: addOns.openServiceMesh } : {}, + (!empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? { omsagent: omsAgentConfig } : {}, + (!empty(addOns.applicationGateway) && addOns.applicationGateway.enabled) ? { ingressApplicationGateway: addOns.applicationGateway } : {} +) + +// Link to existing log analytics workspace when available +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' existing = if (!empty(logAnalyticsName)) { + name: logAnalyticsName +} + +var systemPoolSpec = !empty(systemPoolConfig) ? systemPoolConfig : nodePoolPresets[systemPoolType] + +// Create the primary AKS cluster resources and system node pool +module managedCluster 'aks-managed-cluster.bicep' = { + name: 'managed-cluster' + params: { + name: name + location: location + tags: tags + systemPoolConfig: union( + { name: 'npsystem', mode: 'System' }, + nodePoolBase, + systemPoolSpec + ) + nodeResourceGroupName: nodeResourceGroupName + sku: sku + dnsPrefix: dnsPrefix + kubernetesVersion: kubernetesVersion + addOns: addOnsConfig + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + enableAad: enableAzureRbac && aadTenantId != '' + disableLocalAccounts: disableLocalAccounts + aadTenantId: aadTenantId + enableRbac: enableRbac + enableAzureRbac: enableAzureRbac + webAppRoutingAddon: webAppRoutingAddon + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } +} + +var hasAgentPool = !empty(agentPoolConfig) || !empty(agentPoolType) +var agentPoolSpec = hasAgentPool && !empty(agentPoolConfig) ? agentPoolConfig : empty(agentPoolType) ? {} : nodePoolPresets[agentPoolType] + +// Create additional user agent pool when specified +module agentPool 'aks-agent-pool.bicep' = if (hasAgentPool) { + name: 'aks-node-pool' + params: { + clusterName: managedCluster.outputs.clusterName + name: 'npuserpool' + config: union({ name: 'npuser', mode: 'User' }, nodePoolBase, agentPoolSpec) + } +} + +// Creates container registry (ACR) +module containerRegistry 'container-registry.bicep' = { + name: 'container-registry' + params: { + name: containerRegistryName + location: location + tags: tags + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + } +} + +// Grant ACR Pull access from cluster managed identity to container registry +module containerRegistryAccess '../security/registry-access.bicep' = { + name: 'cluster-container-registry-access' + params: { + containerRegistryName: containerRegistry.outputs.name + principalId: managedCluster.outputs.clusterIdentity.objectId + } +} + +// Give AKS cluster access to the specified principal +module clusterAccess '../security/aks-managed-cluster-access.bicep' = if (enableAzureRbac || disableLocalAccounts) { + name: 'cluster-access' + params: { + clusterName: managedCluster.outputs.clusterName + principalId: principalId + } +} + +// Helpers for node pool configuration +var nodePoolBase = { + osType: 'Linux' + maxPods: 30 + type: 'VirtualMachineScaleSets' + upgradeSettings: { + maxSurge: '33%' + } +} + +var nodePoolPresets = { + CostOptimised: { + vmSize: 'Standard_B4ms' + count: 1 + minCount: 1 + maxCount: 3 + enableAutoScaling: true + availabilityZones: [] + } + Standard: { + vmSize: 'Standard_DS2_v2' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } + HighSpec: { + vmSize: 'Standard_D4s_v3' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } +} + +// Module outputs +@description('The resource name of the AKS cluster') +output clusterName string = managedCluster.outputs.clusterName + +@description('The AKS cluster identity') +output clusterIdentity object = managedCluster.outputs.clusterIdentity + +@description('The resource name of the ACR') +output containerRegistryName string = containerRegistry.outputs.name + +@description('The login server for the container registry') +output containerRegistryLoginServer string = containerRegistry.outputs.loginServer diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/appservice-appsettings.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/appservice-appsettings.bicep new file mode 100644 index 00000000..f4b22f81 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/appservice-appsettings.bicep @@ -0,0 +1,17 @@ +metadata description = 'Updates app settings for an Azure App Service.' +@description('The name of the app service resource within the current resource group scope') +param name string + +@description('The app settings to be applied to the app service') +@secure() +param appSettings object + +resource appService 'Microsoft.Web/sites@2022-03-01' existing = { + name: name +} + +resource settings 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'appsettings' + parent: appService + properties: appSettings +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/appservice.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/appservice.bicep new file mode 100644 index 00000000..bef4d2ba --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/appservice.bicep @@ -0,0 +1,123 @@ +metadata description = 'Creates an Azure App Service in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Microsoft.Web/sites Properties +param kind string = 'app,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = false +param use32BitWorkerProcess bool = false +param ftpsState string = 'FtpsOnly' +param healthCheckPath string = '' + +resource appService 'Microsoft.Web/sites@2022-03-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + serverFarmId: appServicePlanId + siteConfig: { + linuxFxVersion: linuxFxVersion + alwaysOn: alwaysOn + ftpsState: ftpsState + minTlsVersion: '1.2' + appCommandLine: appCommandLine + numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null + minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null + use32BitWorkerProcess: use32BitWorkerProcess + functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null + healthCheckPath: healthCheckPath + cors: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: true + } + + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + + resource basicPublishingCredentialsPoliciesFtp 'basicPublishingCredentialsPolicies' = { + name: 'ftp' + properties: { + allow: false + } + } + + resource basicPublishingCredentialsPoliciesScm 'basicPublishingCredentialsPolicies' = { + name: 'scm' + properties: { + allow: false + } + } +} + +// Updates to the single Microsoft.sites/web/config resources that need to be performed sequentially +// sites/web/config 'appsettings' +module configAppSettings 'appservice-appsettings.bicep' = { + name: '${name}-appSettings' + params: { + name: appService.name + appSettings: union(appSettings, + { + SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) + ENABLE_ORYX_BUILD: string(enableOryxBuild) + }, + runtimeName == 'python' && appCommandLine == '' ? { PYTHON_ENABLE_GUNICORN_MULTIWORKERS: 'true'} : {}, + !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, + !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) + } +} + +// sites/web/config 'logs' +resource configLogs 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'logs' + parent: appService + properties: { + applicationLogs: { fileSystem: { level: 'Verbose' } } + detailedErrorMessages: { enabled: true } + failedRequestsTracing: { enabled: true } + httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } + } + dependsOn: [configAppSettings] +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) { + name: keyVaultName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output identityPrincipalId string = managedIdentity ? appService.identity.principalId : '' +output name string = appService.name +output uri string = 'https://${appService.properties.defaultHostName}' diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/appserviceplan.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/appserviceplan.bicep new file mode 100644 index 00000000..2e37e041 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/appserviceplan.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param kind string = '' +param reserved bool = true +param sku object + +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + kind: kind + properties: { + reserved: reserved + } +} + +output id string = appServicePlan.id +output name string = appServicePlan.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-app-upsert.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-app-upsert.bicep new file mode 100644 index 00000000..3eec62f2 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-app-upsert.bicep @@ -0,0 +1,105 @@ +metadata description = 'Creates or updates an existing Azure Container App.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The environment name for the container apps') +param containerAppsEnvironmentName string + +@description('The number of CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('The amount of memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@allowed([ 'http', 'grpc' ]) +@description('The protocol used by Dapr to connect to the app, e.g., HTTP or gRPC') +param daprAppProtocol string = 'http' + +@description('Enable or disable Dapr for the container app') +param daprEnabled bool = false + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Specifies if the resource already exists') +param exists bool = false + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The name of the container image') +param imageName string = '' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The target port for the container') +param targetPort int = 80 + +resource existingApp 'Microsoft.App/containerApps@2023-04-01-preview' existing = if (exists) { + name: name +} + +module app 'container-app.bicep' = { + name: '${deployment().name}-update' + params: { + name: name + location: location + tags: tags + identityType: identityType + identityName: identityName + ingressEnabled: ingressEnabled + containerName: containerName + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: containerCpuCoreCount + containerMemory: containerMemory + containerMinReplicas: containerMinReplicas + containerMaxReplicas: containerMaxReplicas + daprEnabled: daprEnabled + daprAppId: daprAppId + daprAppProtocol: daprAppProtocol + secrets: secrets + external: external + env: env + imageName: !empty(imageName) ? imageName : exists ? existingApp.properties.template.containers[0].image : '' + targetPort: targetPort + serviceBinds: serviceBinds + } +} + +output defaultDomain string = app.outputs.defaultDomain +output imageName string = app.outputs.imageName +output name string = app.outputs.name +output uri string = app.outputs.uri diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-app.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-app.bicep new file mode 100644 index 00000000..3724086d --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-app.bicep @@ -0,0 +1,162 @@ +metadata description = 'Creates a container app in an Azure Container App environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Allowed origins') +param allowedOrigins array = [] + +@description('Name of the environment for container apps') +param containerAppsEnvironmentName string + +@description('CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('Memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@description('The protocol used by Dapr to connect to the app, e.g., http or grpc') +@allowed([ 'http', 'grpc' ]) +param daprAppProtocol string = 'http' + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Enable Dapr') +param daprEnabled bool = false + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the container image') +param imageName string = '' + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +param revisionMode string = 'Single' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The name of the container apps add-on to use. e.g. redis') +param serviceType string = '' + +@description('The target port for the container') +param targetPort int = 80 + +resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(identityName)) { + name: identityName +} + +// Private registry support requires both an ACR name and a User Assigned managed identity +var usePrivateRegistry = !empty(identityName) && !empty(containerRegistryName) + +// Automatically set to `UserAssigned` when an `identityName` has been set +var normalizedIdentityType = !empty(identityName) ? 'UserAssigned' : identityType + +module containerRegistryAccess '../security/registry-access.bicep' = if (usePrivateRegistry) { + name: '${deployment().name}-registry-access' + params: { + containerRegistryName: containerRegistryName + principalId: usePrivateRegistry ? userIdentity.properties.principalId : '' + } +} + +resource app 'Microsoft.App/containerApps@2023-04-01-preview' = { + name: name + location: location + tags: tags + // It is critical that the identity is granted ACR pull access before the app is created + // otherwise the container app will throw a provision error + // This also forces us to use an user assigned managed identity since there would no way to + // provide the system assigned identity with the ACR pull access before the app is created + dependsOn: usePrivateRegistry ? [ containerRegistryAccess ] : [] + identity: { + type: normalizedIdentityType + userAssignedIdentities: !empty(identityName) && normalizedIdentityType == 'UserAssigned' ? { '${userIdentity.id}': {} } : null + } + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + activeRevisionsMode: revisionMode + ingress: ingressEnabled ? { + external: external + targetPort: targetPort + transport: 'auto' + corsPolicy: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } : null + dapr: daprEnabled ? { + enabled: true + appId: daprAppId + appProtocol: daprAppProtocol + appPort: ingressEnabled ? targetPort : 0 + } : { enabled: false } + secrets: secrets + service: !empty(serviceType) ? { type: serviceType } : null + registries: usePrivateRegistry ? [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userIdentity.id + } + ] : [] + } + template: { + serviceBinds: !empty(serviceBinds) ? serviceBinds : null + containers: [ + { + image: !empty(imageName) ? imageName : 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: containerName + env: env + resources: { + cpu: json(containerCpuCoreCount) + memory: containerMemory + } + } + ] + scale: { + minReplicas: containerMinReplicas + maxReplicas: containerMaxReplicas + } + } + } +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' existing = { + name: containerAppsEnvironmentName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output identityPrincipalId string = normalizedIdentityType == 'None' ? '' : (empty(identityName) ? app.identity.principalId : userIdentity.properties.principalId) +output imageName string = imageName +output name string = app.name +output serviceBind object = !empty(serviceType) ? { serviceId: app.id, name: name } : {} +output uri string = ingressEnabled ? 'https://${app.properties.configuration.ingress.fqdn}' : '' diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-apps-environment.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-apps-environment.bicep new file mode 100644 index 00000000..8633ba48 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-apps-environment.bicep @@ -0,0 +1,41 @@ +metadata description = 'Creates an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the Application Insights resource') +param applicationInsightsName string = '' + +@description('Specifies if Dapr is enabled') +param daprEnabled bool = false + +@description('Name of the Log Analytics workspace') +param logAnalyticsWorkspaceName string + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: logAnalyticsWorkspaceName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output id string = containerAppsEnvironment.id +output name string = containerAppsEnvironment.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-apps.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-apps.bicep new file mode 100644 index 00000000..1c656e28 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-apps.bicep @@ -0,0 +1,40 @@ +metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param containerRegistryName string +param containerRegistryResourceGroupName string = '' +param containerRegistryAdminUserEnabled bool = false +param logAnalyticsWorkspaceName string +param applicationInsightsName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${name}-container-apps-environment' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + logAnalyticsWorkspaceName: logAnalyticsWorkspaceName + applicationInsightsName: applicationInsightsName + } +} + +module containerRegistry 'container-registry.bicep' = { + name: '${name}-container-registry' + scope: !empty(containerRegistryResourceGroupName) ? resourceGroup(containerRegistryResourceGroupName) : resourceGroup() + params: { + name: containerRegistryName + location: location + adminUserEnabled: containerRegistryAdminUserEnabled + tags: tags + } +} + +output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output environmentName string = containerAppsEnvironment.outputs.name +output environmentId string = containerAppsEnvironment.outputs.id + +output registryLoginServer string = containerRegistry.outputs.loginServer +output registryName string = containerRegistry.outputs.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-registry.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-registry.bicep new file mode 100644 index 00000000..9c64531b --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/container-registry.bicep @@ -0,0 +1,83 @@ +metadata description = 'Creates an Azure Container Registry.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Indicates whether admin user is enabled') +param adminUserEnabled bool = false + +@description('Indicates whether anonymous pull is enabled') +param anonymousPullEnabled bool = false + +@description('Indicates whether data endpoint is enabled') +param dataEndpointEnabled bool = false + +@description('Encryption settings') +param encryption object = { + status: 'disabled' +} + +@description('Options for bypassing network rules') +param networkRuleBypassOptions string = 'AzureServices' + +@description('Public network access setting') +param publicNetworkAccess string = 'Enabled' + +@description('SKU settings') +param sku object = { + name: 'Basic' +} + +@description('Zone redundancy setting') +param zoneRedundancy string = 'Disabled' + +@description('The log analytics workspace ID used for logging and monitoring') +param workspaceId string = '' + +// 2022-02-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { + name: name + location: location + tags: tags + sku: sku + properties: { + adminUserEnabled: adminUserEnabled + anonymousPullEnabled: anonymousPullEnabled + dataEndpointEnabled: dataEndpointEnabled + encryption: encryption + networkRuleBypassOptions: networkRuleBypassOptions + publicNetworkAccess: publicNetworkAccess + zoneRedundancy: zoneRedundancy + } +} + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'registry-diagnostics' + scope: containerRegistry + properties: { + workspaceId: workspaceId + logs: [ + { + category: 'ContainerRegistryRepositoryEvents' + enabled: true + } + { + category: 'ContainerRegistryLoginEvents' + enabled: true + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + timeGrain: 'PT1M' + } + ] + } +} + +output loginServer string = containerRegistry.properties.loginServer +output name string = containerRegistry.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/functions.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/functions.bicep new file mode 100644 index 00000000..7070a2c6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/functions.bicep @@ -0,0 +1,86 @@ +metadata description = 'Creates an Azure Function in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) +param storageAccountName string + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Function Settings +@allowed([ + '~4', '~3', '~2', '~1' +]) +param extensionVersion string = '~4' + +// Microsoft.Web/sites Properties +param kind string = 'functionapp,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = true +param use32BitWorkerProcess bool = false +param healthCheckPath string = '' + +module functions 'appservice.bicep' = { + name: '${name}-functions' + params: { + name: name + location: location + tags: tags + allowedOrigins: allowedOrigins + alwaysOn: alwaysOn + appCommandLine: appCommandLine + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: union(appSettings, { + AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${storage.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' + FUNCTIONS_EXTENSION_VERSION: extensionVersion + FUNCTIONS_WORKER_RUNTIME: runtimeName + }) + clientAffinityEnabled: clientAffinityEnabled + enableOryxBuild: enableOryxBuild + functionAppScaleLimit: functionAppScaleLimit + healthCheckPath: healthCheckPath + keyVaultName: keyVaultName + kind: kind + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + minimumElasticInstanceCount: minimumElasticInstanceCount + numberOfWorkers: numberOfWorkers + runtimeName: runtimeName + runtimeVersion: runtimeVersion + runtimeNameAndVersion: runtimeNameAndVersion + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + use32BitWorkerProcess: use32BitWorkerProcess + } +} + +resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { + name: storageAccountName +} + +output identityPrincipalId string = managedIdentity ? functions.outputs.identityPrincipalId : '' +output name string = functions.outputs.name +output uri string = functions.outputs.uri diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/host/staticwebapp.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/staticwebapp.bicep new file mode 100644 index 00000000..cedaf906 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/host/staticwebapp.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Static Web Apps instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'Free' + tier: 'Free' +} + +resource web 'Microsoft.Web/staticSites@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + properties: { + provider: 'Custom' + } +} + +output name string = web.name +output uri string = 'https://${web.properties.defaultHostname}' diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/applicationinsights-dashboard.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/applicationinsights-dashboard.bicep new file mode 100644 index 00000000..d082e668 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/applicationinsights-dashboard.bicep @@ -0,0 +1,1236 @@ +metadata description = 'Creates a dashboard for an Application Insights instance.' +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/applicationinsights.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/applicationinsights.bicep new file mode 100644 index 00000000..4b4d01e3 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/applicationinsights.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' +param name string +param dashboardName string = '' +param location string = resourceGroup().location +param tags object = {} +param logAnalyticsWorkspaceId string + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { + name: 'application-insights-dashboard' + params: { + name: dashboardName + location: location + applicationInsightsName: applicationInsights.name + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/loganalytics.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/loganalytics.bicep new file mode 100644 index 00000000..33f9dc29 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/loganalytics.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates a Log Analytics workspace.' +param name string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: name + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/monitoring.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/monitoring.bicep new file mode 100644 index 00000000..6bb05b0b --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/monitor/monitoring.bicep @@ -0,0 +1,32 @@ +metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string = '' +param location string = resourceGroup().location +param tags object = {} + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + name: logAnalyticsName + location: location + tags: tags + } +} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/networking/cdn-endpoint.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/networking/cdn-endpoint.bicep new file mode 100644 index 00000000..5e8ab695 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/networking/cdn-endpoint.bicep @@ -0,0 +1,52 @@ +metadata description = 'Adds an endpoint to an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The name of the CDN profile resource') +@minLength(1) +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('The origin URL for the endpoint') +@minLength(1) +param originUrl string + +resource endpoint 'Microsoft.Cdn/profiles/endpoints@2022-05-01-preview' = { + parent: cdnProfile + name: name + location: location + tags: tags + properties: { + originHostHeader: originUrl + isHttpAllowed: false + isHttpsAllowed: true + queryStringCachingBehavior: 'UseQueryString' + optimizationType: 'GeneralWebDelivery' + origins: [ + { + name: replace(originUrl, '.', '-') + properties: { + hostName: originUrl + originHostHeader: originUrl + priority: 1 + weight: 1000 + enabled: true + } + } + ] + deliveryPolicy: { + rules: deliveryPolicyRules + } + } +} + +resource cdnProfile 'Microsoft.Cdn/profiles@2022-05-01-preview' existing = { + name: cdnProfileName +} + +output id string = endpoint.id +output name string = endpoint.name +output uri string = 'https://${endpoint.properties.hostName}' diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/networking/cdn-profile.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/networking/cdn-profile.bicep new file mode 100644 index 00000000..27669ee2 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/networking/cdn-profile.bicep @@ -0,0 +1,34 @@ +metadata description = 'Creates an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The pricing tier of this CDN profile') +@allowed([ + 'Custom_Verizon' + 'Premium_AzureFrontDoor' + 'Premium_Verizon' + 'StandardPlus_955BandWidth_ChinaCdn' + 'StandardPlus_AvgBandWidth_ChinaCdn' + 'StandardPlus_ChinaCdn' + 'Standard_955BandWidth_ChinaCdn' + 'Standard_Akamai' + 'Standard_AvgBandWidth_ChinaCdn' + 'Standard_AzureFrontDoor' + 'Standard_ChinaCdn' + 'Standard_Microsoft' + 'Standard_Verizon' +]) +param sku string = 'Standard_Microsoft' + +resource profile 'Microsoft.Cdn/profiles@2022-05-01-preview' = { + name: name + location: location + tags: tags + sku: { + name: sku + } +} + +output id string = profile.id +output name string = profile.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/networking/cdn.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/networking/cdn.bicep new file mode 100644 index 00000000..de98a1f9 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/networking/cdn.bicep @@ -0,0 +1,42 @@ +metadata description = 'Creates an Azure CDN profile with a single endpoint.' +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the CDN endpoint resource') +param cdnEndpointName string + +@description('Name of the CDN profile resource') +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('Origin URL for the CDN endpoint') +param originUrl string + +module cdnProfile 'cdn-profile.bicep' = { + name: 'cdn-profile' + params: { + name: cdnProfileName + location: location + tags: tags + } +} + +module cdnEndpoint 'cdn-endpoint.bicep' = { + name: 'cdn-endpoint' + params: { + name: cdnEndpointName + location: location + tags: tags + cdnProfileName: cdnProfile.outputs.name + originUrl: originUrl + deliveryPolicyRules: deliveryPolicyRules + } +} + +output endpointName string = cdnEndpoint.outputs.name +output endpointId string = cdnEndpoint.outputs.id +output profileName string = cdnProfile.outputs.name +output profileId string = cdnProfile.outputs.id +output uri string = cdnEndpoint.outputs.uri diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/search/search-services.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/search/search-services.bicep new file mode 100644 index 00000000..d9c619a9 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/search/search-services.bicep @@ -0,0 +1,68 @@ +metadata description = 'Creates an Azure AI Search instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'standard' +} + +param authOptions object = {} +param disableLocalAuth bool = false +param disabledDataExfiltrationOptions array = [] +param encryptionWithCmk object = { + enforcement: 'Unspecified' +} +@allowed([ + 'default' + 'highDensity' +]) +param hostingMode string = 'default' +param networkRuleSet object = { + bypass: 'None' + ipRules: [] +} +param partitionCount int = 1 +@allowed([ + 'enabled' + 'disabled' +]) +param publicNetworkAccess string = 'enabled' +param replicaCount int = 1 +@allowed([ + 'disabled' + 'free' + 'standard' +]) +param semanticSearch string = 'disabled' + +var searchIdentityProvider = (sku.name == 'free') ? null : { + type: 'SystemAssigned' +} + +resource search 'Microsoft.Search/searchServices@2021-04-01-preview' = { + name: name + location: location + tags: tags + // The free tier does not support managed identity + identity: searchIdentityProvider + properties: { + authOptions: authOptions + disableLocalAuth: disableLocalAuth + disabledDataExfiltrationOptions: disabledDataExfiltrationOptions + encryptionWithCmk: encryptionWithCmk + hostingMode: hostingMode + networkRuleSet: networkRuleSet + partitionCount: partitionCount + publicNetworkAccess: publicNetworkAccess + replicaCount: replicaCount + semanticSearch: semanticSearch + } + sku: sku +} + +output id string = search.id +output endpoint string = 'https://${name}.search.windows.net/' +output name string = search.name +output principalId string = !empty(searchIdentityProvider) ? search.identity.principalId : '' + diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/security/aks-managed-cluster-access.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/aks-managed-cluster-access.bicep new file mode 100644 index 00000000..dec984e8 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/aks-managed-cluster-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns RBAC role to the specified AKS cluster and principal.' +param clusterName string +param principalId string + +var aksClusterAdminRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b') + +resource aksRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aksCluster // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, aksClusterAdminRole) + properties: { + roleDefinitionId: aksClusterAdminRole + principalType: 'User' + principalId: principalId + } +} + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/security/keyvault-access.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/keyvault-access.bicep new file mode 100644 index 00000000..316775f2 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/keyvault-access.bicep @@ -0,0 +1,22 @@ +metadata description = 'Assigns an Azure Key Vault access policy.' +param name string = 'add' + +param keyVaultName string +param permissions object = { secrets: [ 'get', 'list' ] } +param principalId string + +resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = { + parent: keyVault + name: name + properties: { + accessPolicies: [ { + objectId: principalId + tenantId: subscription().tenantId + permissions: permissions + } ] + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/security/keyvault-secret.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/keyvault-secret.bicep new file mode 100644 index 00000000..7441b296 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/keyvault-secret.bicep @@ -0,0 +1,31 @@ +metadata description = 'Creates or updates a secret in an Azure Key Vault.' +param name string +param tags object = {} +param keyVaultName string +param contentType string = 'string' +@description('The value of the secret. Provide only derived values like blob storage access, but do not hard code any secrets in your templates') +@secure() +param secretValue string + +param enabled bool = true +param exp int = 0 +param nbf int = 0 + +resource keyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + name: name + tags: tags + parent: keyVault + properties: { + attributes: { + enabled: enabled + exp: exp + nbf: nbf + } + contentType: contentType + value: secretValue + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/security/keyvault.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/keyvault.bicep new file mode 100644 index 00000000..314a1db6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/keyvault.bicep @@ -0,0 +1,26 @@ +metadata description = 'Creates an Azure Key Vault.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param principalId string = '' + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: name + location: location + tags: tags + properties: { + tenantId: subscription().tenantId + sku: { family: 'A', name: 'standard' } + accessPolicies: !empty(principalId) ? [ + { + objectId: principalId + permissions: { secrets: [ 'get', 'list' ] } + tenantId: subscription().tenantId + } + ] : [] + } +} + +output endpoint string = keyVault.properties.vaultUri +output name string = keyVault.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/security/registry-access.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/registry-access.bicep new file mode 100644 index 00000000..5335efab --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/registry-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns ACR Pull permissions to access an Azure Container Registry.' +param containerRegistryName string +param principalId string + +var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + +resource aksAcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: containerRegistry // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, acrPullRole) + properties: { + roleDefinitionId: acrPullRole + principalType: 'ServicePrincipal' + principalId: principalId + } +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { + name: containerRegistryName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/security/role.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/role.bicep new file mode 100644 index 00000000..0b30cfd3 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/security/role.bicep @@ -0,0 +1,21 @@ +metadata description = 'Creates a role assignment for a service principal.' +param principalId string + +@allowed([ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' +]) +param principalType string = 'ServicePrincipal' +param roleDefinitionId string + +resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) + properties: { + principalId: principalId + principalType: principalType + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) + } +} diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/storage/storage-account.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/storage/storage-account.bicep new file mode 100644 index 00000000..4b6febbe --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/storage/storage-account.bicep @@ -0,0 +1,64 @@ +metadata description = 'Creates an Azure storage account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@allowed([ + 'Cool' + 'Hot' + 'Premium' ]) +param accessTier string = 'Hot' +param allowBlobPublicAccess bool = true +param allowCrossTenantReplication bool = true +param allowSharedKeyAccess bool = true +param containers array = [] +param defaultToOAuthAuthentication bool = false +param deleteRetentionPolicy object = {} +@allowed([ 'AzureDnsZone', 'Standard' ]) +param dnsEndpointType string = 'Standard' +param kind string = 'StorageV2' +param minimumTlsVersion string = 'TLS1_2' +param supportsHttpsTrafficOnly bool = true +param networkAcls object = { + bypass: 'AzureServices' + defaultAction: 'Allow' +} +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { name: 'Standard_LRS' } + +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { + name: name + location: location + tags: tags + kind: kind + sku: sku + properties: { + accessTier: accessTier + allowBlobPublicAccess: allowBlobPublicAccess + allowCrossTenantReplication: allowCrossTenantReplication + allowSharedKeyAccess: allowSharedKeyAccess + defaultToOAuthAuthentication: defaultToOAuthAuthentication + dnsEndpointType: dnsEndpointType + minimumTlsVersion: minimumTlsVersion + networkAcls: networkAcls + publicNetworkAccess: publicNetworkAccess + supportsHttpsTrafficOnly: supportsHttpsTrafficOnly + } + + resource blobServices 'blobServices' = if (!empty(containers)) { + name: 'default' + properties: { + deleteRetentionPolicy: deleteRetentionPolicy + } + resource container 'containers' = [for container in containers: { + name: container.name + properties: { + publicAccess: contains(container, 'publicAccess') ? container.publicAccess : 'None' + } + }] + } +} + +output name string = storage.name +output primaryEndpoints object = storage.properties.primaryEndpoints diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/core/testing/loadtesting.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/core/testing/loadtesting.bicep new file mode 100644 index 00000000..46781086 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/core/testing/loadtesting.bicep @@ -0,0 +1,15 @@ +param name string +param location string = resourceGroup().location +param managedIdentity bool = false +param tags object = {} + +resource loadTest 'Microsoft.LoadTestService/loadTests@2022-12-01' = { + name: name + location: location + tags: tags + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + properties: { + } +} + +output loadTestingName string = loadTest.name diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/main.bicep b/Environments/Contoso-Base-Shared-AKS-Prod/main.bicep new file mode 100644 index 00000000..c283ef31 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/main.bicep @@ -0,0 +1,55 @@ +@minLength(1) +@maxLength(64) +@description('Name of the the environment which is used to generate a short unique hash used in all resources.') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string = resourceGroup().location + +@description('The resource name of the AKS cluster') +param clusterName string = '' + +@description('The resource name of the Container Registry (ACR)') +param containerRegistryName string = '' + +param applicationInsightsDashboardName string = '' +param applicationInsightsName string = '' +param logAnalyticsName string = '' + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +// The AKS cluster to host applications +module aks './core/host/aks.bicep' = { + name: 'aks' + params: { + location: location + name: !empty(clusterName) ? clusterName : '${abbrs.containerServiceManagedClusters}${resourceToken}' + containerRegistryName: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + logAnalyticsName: monitoring.outputs.logAnalyticsWorkspaceName + } +} + +// Monitor application with Azure Monitor +module monitoring './core/monitor/monitoring.bicep' = { + name: 'monitoring' + params: { + location: location + tags: tags + logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}' + } +} + +// App outputs +output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId +output AZURE_AKS_CLUSTER_NAME string = aks.outputs.clusterName +output AZURE_AKS_IDENTITY_CLIENT_ID string = aks.outputs.clusterIdentity.clientId +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = aks.outputs.containerRegistryLoginServer +output AZURE_CONTAINER_REGISTRY_NAME string = aks.outputs.containerRegistryName +output REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/main.parameters.json b/Environments/Contoso-Base-Shared-AKS-Prod/main.parameters.json new file mode 100644 index 00000000..67ad8524 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/main.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "principalId": { + "value": "${AZURE_PRINCIPAL_ID}" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-AKS-Prod/manifest.yaml b/Environments/Contoso-Base-Shared-AKS-Prod/manifest.yaml new file mode 100644 index 00000000..a8cb75cd --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Prod/manifest.yaml @@ -0,0 +1,21 @@ +name: Contoso-Base-Shared-AKS-Prod +version: 1.0.0 +summary: Base shared environment for app development in AKS +description: Deploys base shared env (Container environment and monitoring) for app development in AKS +runner: ARM +templatePath: azuredeploy.json + +parameters: +- id: "environmentName" + name: "Environment Name (e.g. testenv)" + description: "Name of the Environment" + type: string + required: true + +- id: "location" + name: "Region (e.g. eastus)" + description: "Location of the resources" + type: string + required: true + + diff --git a/Environments/Contoso-Base-Shared-AKS-Test/abbreviations.json b/Environments/Contoso-Base-Shared-AKS-Test/abbreviations.json new file mode 100644 index 00000000..289a08aa --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/abbreviations.json @@ -0,0 +1,136 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-AKS-Test/app/db.bicep b/Environments/Contoso-Base-Shared-AKS-Test/app/db.bicep new file mode 100644 index 00000000..938949c6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/app/db.bicep @@ -0,0 +1,40 @@ +param accountName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [ + { + name: 'TodoList' + id: 'TodoList' + shardKey: 'Hash' + indexKey: '_id' + } + { + name: 'TodoItem' + id: 'TodoItem' + shardKey: 'Hash' + indexKey: '_id' + } +] +param databaseName string = '' +param keyVaultName string + +// Because databaseName is optional in main.bicep, we make sure the database name is set here. +var defaultDatabaseName = 'Todo' +var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName + +module cosmos '../core/database/cosmos/mongo/cosmos-mongo-db.bicep' = { + name: 'cosmos-mongo' + params: { + accountName: accountName + databaseName: actualDatabaseName + location: location + collections: collections + keyVaultName: keyVaultName + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = cosmos.outputs.databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/Contoso-Base-Shared-AKS-Test/azuredeploy.json b/Environments/Contoso-Base-Shared-AKS-Test/azuredeploy.json new file mode 100644 index 00000000..0b774988 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/azuredeploy.json @@ -0,0 +1,2757 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "11426766062419716265" + } + }, + "parameters": { + "environmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the the environment which is used to generate a short unique hash used in all resources." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "minLength": 1, + "metadata": { + "description": "Primary location for all resources" + } + }, + "clusterName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource name of the AKS cluster" + } + }, + "containerRegistryName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource name of the Container Registry (ACR)" + } + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "" + } + }, + "variables": { + "$fxv#0": { + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "loadTesting": "lt-", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" + }, + "abbrs": "[variables('$fxv#0')]", + "resourceToken": "[toLower(uniqueString(subscription().id, parameters('environmentName'), parameters('location')))]", + "tags": { + "azd-env-name": "[parameters('environmentName')]" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aks", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "name": "[if(not(empty(parameters('clusterName'))), createObject('value', parameters('clusterName')), createObject('value', format('{0}{1}', variables('abbrs').containerServiceManagedClusters, variables('resourceToken'))))]", + "containerRegistryName": "[if(not(empty(parameters('containerRegistryName'))), createObject('value', parameters('containerRegistryName')), createObject('value', format('{0}{1}', variables('abbrs').containerRegistryRegistries, variables('resourceToken'))))]", + "logAnalyticsName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.logAnalyticsWorkspaceName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "13278918148041449332" + }, + "description": "Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool as well as an additional user agent pool." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name for the AKS managed cluster" + } + }, + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The name for the Azure container registry (ACR)" + } + }, + "logAnalyticsName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the connected log analytics workspace" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The Azure region/location for the AKS resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom tags to apply to the AKS resources" + } + }, + "addOns": { + "type": "object", + "defaultValue": { + "azurePolicy": { + "enabled": true, + "config": { + "version": "v2" + } + }, + "keyVault": { + "enabled": true, + "config": { + "enableSecretRotation": "true", + "rotationPollInterval": "2m" + } + }, + "openServiceMesh": { + "enabled": false, + "config": {} + }, + "omsAgent": { + "enabled": true, + "config": {} + }, + "applicationGateway": { + "enabled": false, + "config": {} + } + }, + "metadata": { + "description": "AKS add-ons configuration" + } + }, + "sku": { + "type": "string", + "defaultValue": "Free", + "allowedValues": [ + "Free", + "Paid", + "Standard" + ], + "metadata": { + "description": "The managed cluster SKU." + } + }, + "loadBalancerSku": { + "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "basic", + "standard" + ], + "metadata": { + "description": "The load balancer SKU to use for ingress into the AKS cluster" + } + }, + "networkPlugin": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "kubenet", + "none" + ], + "metadata": { + "description": "Network plugin used for building the Kubernetes network." + } + }, + "networkPolicy": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "calico" + ], + "metadata": { + "description": "Network policy used for building the Kubernetes network." + } + }, + "dnsPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The DNS prefix to associate with the AKS cluster" + } + }, + "nodeResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the resource group for the managed resources of the AKS cluster" + } + }, + "systemPoolType": { + "type": "string", + "defaultValue": "CostOptimised", + "allowedValues": [ + "CostOptimised", + "Standard", + "HighSpec", + "Custom" + ], + "metadata": { + "description": "The System Pool Preset sizing" + } + }, + "agentPoolType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "CostOptimised", + "Standard", + "HighSpec", + "Custom" + ], + "metadata": { + "description": "The User Pool Preset sizing" + } + }, + "systemPoolConfig": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom configuration of system node pool" + } + }, + "agentPoolConfig": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom configuration of user node pool" + } + }, + "principalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Id of the user or app to assign application roles" + } + }, + "kubernetesVersion": { + "type": "string", + "defaultValue": "1.27.7", + "metadata": { + "description": "Kubernetes Version" + } + }, + "aadTenantId": { + "type": "string", + "defaultValue": "[tenant().tenantId]", + "metadata": { + "description": "The Tenant ID associated to the Azure Active Directory" + } + }, + "enableRbac": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether RBAC is enabled for local accounts" + } + }, + "disableLocalAccounts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "If set to true, getting static credentials will be disabled for this cluster." + } + }, + "enableAzureRbac": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable RBAC using AAD" + } + }, + "webAppRoutingAddon": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether web app routing (preview) add-on is enabled" + } + } + }, + "variables": { + "omsAgentConfig": "[if(and(and(not(empty(parameters('logAnalyticsName'))), not(empty(parameters('addOns').omsAgent))), parameters('addOns').omsAgent.enabled), union(parameters('addOns').omsAgent, createObject('config', createObject('logAnalyticsWorkspaceResourceID', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))))), createObject())]", + "addOnsConfig": "[union(if(and(not(empty(parameters('addOns').azurePolicy)), parameters('addOns').azurePolicy.enabled), createObject('azurepolicy', parameters('addOns').azurePolicy), createObject()), if(and(not(empty(parameters('addOns').keyVault)), parameters('addOns').keyVault.enabled), createObject('azureKeyvaultSecretsProvider', parameters('addOns').keyVault), createObject()), if(and(not(empty(parameters('addOns').openServiceMesh)), parameters('addOns').openServiceMesh.enabled), createObject('openServiceMesh', parameters('addOns').openServiceMesh), createObject()), if(and(not(empty(parameters('addOns').omsAgent)), parameters('addOns').omsAgent.enabled), createObject('omsagent', variables('omsAgentConfig')), createObject()), if(and(not(empty(parameters('addOns').applicationGateway)), parameters('addOns').applicationGateway.enabled), createObject('ingressApplicationGateway', parameters('addOns').applicationGateway), createObject()))]", + "systemPoolSpec": "[if(not(empty(parameters('systemPoolConfig'))), parameters('systemPoolConfig'), variables('nodePoolPresets')[parameters('systemPoolType')])]", + "hasAgentPool": "[or(not(empty(parameters('agentPoolConfig'))), not(empty(parameters('agentPoolType'))))]", + "agentPoolSpec": "[if(and(variables('hasAgentPool'), not(empty(parameters('agentPoolConfig')))), parameters('agentPoolConfig'), if(empty(parameters('agentPoolType')), createObject(), variables('nodePoolPresets')[parameters('agentPoolType')]))]", + "nodePoolBase": { + "osType": "Linux", + "maxPods": 30, + "type": "VirtualMachineScaleSets", + "upgradeSettings": { + "maxSurge": "33%" + } + }, + "nodePoolPresets": { + "CostOptimised": { + "vmSize": "Standard_B4ms", + "count": 1, + "minCount": 1, + "maxCount": 3, + "enableAutoScaling": true, + "availabilityZones": [] + }, + "Standard": { + "vmSize": "Standard_DS2_v2", + "count": 3, + "minCount": 3, + "maxCount": 5, + "enableAutoScaling": true, + "availabilityZones": [ + "1", + "2", + "3" + ] + }, + "HighSpec": { + "vmSize": "Standard_D4s_v3", + "count": 3, + "minCount": 3, + "maxCount": 5, + "enableAutoScaling": true, + "availabilityZones": [ + "1", + "2", + "3" + ] + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "managed-cluster", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "systemPoolConfig": { + "value": "[union(createObject('name', 'npsystem', 'mode', 'System'), variables('nodePoolBase'), variables('systemPoolSpec'))]" + }, + "nodeResourceGroupName": { + "value": "[parameters('nodeResourceGroupName')]" + }, + "sku": { + "value": "[parameters('sku')]" + }, + "dnsPrefix": { + "value": "[parameters('dnsPrefix')]" + }, + "kubernetesVersion": { + "value": "[parameters('kubernetesVersion')]" + }, + "addOns": { + "value": "[variables('addOnsConfig')]" + }, + "workspaceId": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))), createObject('value', ''))]", + "enableAad": { + "value": "[and(parameters('enableAzureRbac'), not(equals(parameters('aadTenantId'), '')))]" + }, + "disableLocalAccounts": { + "value": "[parameters('disableLocalAccounts')]" + }, + "aadTenantId": { + "value": "[parameters('aadTenantId')]" + }, + "enableRbac": { + "value": "[parameters('enableRbac')]" + }, + "enableAzureRbac": { + "value": "[parameters('enableAzureRbac')]" + }, + "webAppRoutingAddon": { + "value": "[parameters('webAppRoutingAddon')]" + }, + "loadBalancerSku": { + "value": "[parameters('loadBalancerSku')]" + }, + "networkPlugin": { + "value": "[parameters('networkPlugin')]" + }, + "networkPolicy": { + "value": "[parameters('networkPolicy')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8184151159222198677" + }, + "description": "Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "The name for the AKS managed cluster" + } + }, + "nodeResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the resource group for the managed resources of the AKS cluster" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The Azure region/location for the AKS resources" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Custom tags to apply to the AKS resources" + } + }, + "kubernetesVersion": { + "type": "string", + "defaultValue": "1.27.7", + "metadata": { + "description": "Kubernetes Version" + } + }, + "enableRbac": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether RBAC is enabled for local accounts" + } + }, + "webAppRoutingAddon": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Whether web app routing (preview) add-on is enabled" + } + }, + "enableAad": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable Azure Active Directory integration" + } + }, + "enableAzureRbac": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable RBAC using AAD" + } + }, + "aadTenantId": { + "type": "string", + "defaultValue": "[tenant().tenantId]", + "metadata": { + "description": "The Tenant ID associated to the Azure Active Directory" + } + }, + "loadBalancerSku": { + "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "basic", + "standard" + ], + "metadata": { + "description": "The load balancer SKU to use for ingress into the AKS cluster" + } + }, + "networkPlugin": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "kubenet", + "none" + ], + "metadata": { + "description": "Network plugin used for building the Kubernetes network." + } + }, + "networkPolicy": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "calico" + ], + "metadata": { + "description": "Network policy used for building the Kubernetes network." + } + }, + "disableLocalAccounts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "If set to true, getting static credentials will be disabled for this cluster." + } + }, + "sku": { + "type": "string", + "defaultValue": "Free", + "allowedValues": [ + "Free", + "Paid", + "Standard" + ], + "metadata": { + "description": "The managed cluster SKU." + } + }, + "addOns": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Configuration of AKS add-ons" + } + }, + "workspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The log analytics workspace id used for logging & monitoring" + } + }, + "systemPoolConfig": { + "type": "object", + "metadata": { + "description": "The node pool configuration for the System agent pool" + } + }, + "dnsPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The DNS prefix to associate with the AKS cluster" + } + } + }, + "variables": { + "aksDiagCategories": [ + "cluster-autoscaler", + "kube-controller-manager", + "kube-audit-admin", + "guard" + ] + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters", + "apiVersion": "2023-10-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": { + "type": "SystemAssigned" + }, + "sku": { + "name": "Base", + "tier": "[parameters('sku')]" + }, + "properties": { + "nodeResourceGroup": "[if(not(empty(parameters('nodeResourceGroupName'))), parameters('nodeResourceGroupName'), format('rg-mc-{0}', parameters('name')))]", + "kubernetesVersion": "[parameters('kubernetesVersion')]", + "dnsPrefix": "[if(empty(parameters('dnsPrefix')), format('{0}-dns', parameters('name')), parameters('dnsPrefix'))]", + "enableRBAC": "[parameters('enableRbac')]", + "aadProfile": "[if(parameters('enableAad'), createObject('managed', true(), 'enableAzureRBAC', parameters('enableAzureRbac'), 'tenantID', parameters('aadTenantId')), null())]", + "agentPoolProfiles": [ + "[parameters('systemPoolConfig')]" + ], + "networkProfile": { + "loadBalancerSku": "[parameters('loadBalancerSku')]", + "networkPlugin": "[parameters('networkPlugin')]", + "networkPolicy": "[parameters('networkPolicy')]" + }, + "disableLocalAccounts": "[and(parameters('disableLocalAccounts'), parameters('enableAad'))]", + "addonProfiles": "[parameters('addOns')]", + "ingressProfile": { + "webAppRouting": { + "enabled": "[parameters('webAppRoutingAddon')]" + } + } + } + }, + { + "condition": "[not(empty(parameters('workspaceId')))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerService/managedClusters/{0}', parameters('name'))]", + "name": "aks-diagnostics", + "properties": { + "copy": [ + { + "name": "logs", + "count": "[length(variables('aksDiagCategories'))]", + "input": { + "category": "[variables('aksDiagCategories')[copyIndex('logs')]]", + "enabled": true + } + } + ], + "workspaceId": "[parameters('workspaceId')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerService/managedClusters', parameters('name'))]" + ] + } + ], + "outputs": { + "clusterName": { + "type": "string", + "metadata": { + "description": "The resource name of the AKS cluster" + }, + "value": "[parameters('name')]" + }, + "clusterIdentity": { + "type": "object", + "metadata": { + "description": "The AKS cluster identity" + }, + "value": { + "clientId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.clientId]", + "objectId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.objectId]", + "resourceId": "[reference(resourceId('Microsoft.ContainerService/managedClusters', parameters('name')), '2023-10-02-preview').identityProfile.kubeletidentity.resourceId]" + } + } + } + } + } + }, + { + "condition": "[variables('hasAgentPool')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aks-node-pool", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "name": { + "value": "npuserpool" + }, + "config": { + "value": "[union(createObject('name', 'npuser', 'mode', 'User'), variables('nodePoolBase'), variables('agentPoolSpec'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "6072270897511874144" + }, + "description": "Adds an agent pool to an Azure Kubernetes Service (AKS) cluster." + }, + "parameters": { + "clusterName": { + "type": "string" + }, + "name": { + "type": "string", + "metadata": { + "description": "The agent pool name" + } + }, + "config": { + "type": "object", + "metadata": { + "description": "The agent pool configuration" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters/agentPools", + "apiVersion": "2023-10-02-preview", + "name": "[format('{0}/{1}', parameters('clusterName'), parameters('name'))]", + "properties": "[parameters('config')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "container-registry", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('containerRegistryName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "workspaceId": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsName'))), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "12834334744516280883" + }, + "description": "Creates an Azure Container Registry." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "adminUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether admin user is enabled" + } + }, + "anonymousPullEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether anonymous pull is enabled" + } + }, + "dataEndpointEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Indicates whether data endpoint is enabled" + } + }, + "encryption": { + "type": "object", + "defaultValue": { + "status": "disabled" + }, + "metadata": { + "description": "Encryption settings" + } + }, + "networkRuleBypassOptions": { + "type": "string", + "defaultValue": "AzureServices", + "metadata": { + "description": "Options for bypassing network rules" + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Public network access setting" + } + }, + "sku": { + "type": "object", + "defaultValue": { + "name": "Basic" + }, + "metadata": { + "description": "SKU settings" + } + }, + "zoneRedundancy": { + "type": "string", + "defaultValue": "Disabled", + "metadata": { + "description": "Zone redundancy setting" + } + }, + "workspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The log analytics workspace ID used for logging and monitoring" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2022-02-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": "[parameters('sku')]", + "properties": { + "adminUserEnabled": "[parameters('adminUserEnabled')]", + "anonymousPullEnabled": "[parameters('anonymousPullEnabled')]", + "dataEndpointEnabled": "[parameters('dataEndpointEnabled')]", + "encryption": "[parameters('encryption')]", + "networkRuleBypassOptions": "[parameters('networkRuleBypassOptions')]", + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "zoneRedundancy": "[parameters('zoneRedundancy')]" + } + }, + { + "condition": "[not(empty(parameters('workspaceId')))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "registry-diagnostics", + "properties": { + "workspaceId": "[parameters('workspaceId')]", + "logs": [ + { + "category": "ContainerRegistryRepositoryEvents", + "enabled": true + }, + { + "category": "ContainerRegistryLoginEvents", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true, + "timeGrain": "PT1M" + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" + ] + } + ], + "outputs": { + "loginServer": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), '2022-02-01-preview').loginServer]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cluster-container-registry-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerRegistryName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.name.value]" + }, + "principalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterIdentity.value.objectId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "15144906240959446537" + }, + "description": "Assigns ACR Pull permissions to access an Azure Container Registry." + }, + "parameters": { + "containerRegistryName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "acrPullRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('containerRegistryName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('acrPullRole'))]", + "properties": { + "roleDefinitionId": "[variables('acrPullRole')]", + "principalType": "ServicePrincipal", + "principalId": "[parameters('principalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'container-registry')]", + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + }, + { + "condition": "[or(parameters('enableAzureRbac'), parameters('disableLocalAccounts'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "cluster-access", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "principalId": { + "value": "[parameters('principalId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "8205013527918052324" + }, + "description": "Assigns RBAC role to the specified AKS cluster and principal." + }, + "parameters": { + "clusterName": { + "type": "string" + }, + "principalId": { + "type": "string" + } + }, + "variables": { + "aksClusterAdminRole": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b')]" + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerService/managedClusters/{0}', parameters('clusterName'))]", + "name": "[guid(subscription().id, resourceGroup().id, parameters('principalId'), variables('aksClusterAdminRole'))]", + "properties": { + "roleDefinitionId": "[variables('aksClusterAdminRole')]", + "principalType": "User", + "principalId": "[parameters('principalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'managed-cluster')]" + ] + } + ], + "outputs": { + "clusterName": { + "type": "string", + "metadata": { + "description": "The resource name of the AKS cluster" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterName.value]" + }, + "clusterIdentity": { + "type": "object", + "metadata": { + "description": "The AKS cluster identity" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'managed-cluster'), '2022-09-01').outputs.clusterIdentity.value]" + }, + "containerRegistryName": { + "type": "string", + "metadata": { + "description": "The resource name of the ACR" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.name.value]" + }, + "containerRegistryLoginServer": { + "type": "string", + "metadata": { + "description": "The login server for the container registry" + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'container-registry'), '2022-09-01').outputs.loginServer.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'monitoring')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "monitoring", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "logAnalyticsName": "[if(not(empty(parameters('logAnalyticsName'))), createObject('value', parameters('logAnalyticsName')), createObject('value', format('{0}{1}', variables('abbrs').operationalInsightsWorkspaces, variables('resourceToken'))))]", + "applicationInsightsName": "[if(not(empty(parameters('applicationInsightsName'))), createObject('value', parameters('applicationInsightsName')), createObject('value', format('{0}{1}', variables('abbrs').insightsComponents, variables('resourceToken'))))]", + "applicationInsightsDashboardName": "[if(not(empty(parameters('applicationInsightsDashboardName'))), createObject('value', parameters('applicationInsightsDashboardName')), createObject('value', format('{0}{1}', variables('abbrs').portalDashboards, variables('resourceToken'))))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "10041669792322197047" + }, + "description": "Creates an Application Insights instance and a Log Analytics workspace." + }, + "parameters": { + "logAnalyticsName": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "applicationInsightsDashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "loganalytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('logAnalyticsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "9622176141085970536" + }, + "description": "Creates a Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-12-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": 30, + "features": { + "searchVersion": 1 + }, + "sku": { + "name": "PerGB2018" + } + } + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "applicationinsights", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('applicationInsightsName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "dashboardName": { + "value": "[parameters('applicationInsightsDashboardName')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "1335628967363670282" + }, + "description": "Creates an Application Insights instance based on an existing Log Analytics workspace." + }, + "parameters": { + "name": { + "type": "string" + }, + "dashboardName": { + "type": "string", + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + }, + "logAnalyticsWorkspaceId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]" + } + }, + { + "condition": "[not(empty(parameters('dashboardName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "application-insights-dashboard", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('dashboardName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "applicationInsightsName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "2145880658446193205" + }, + "description": "Creates a dashboard for an Application Insights instance." + }, + "parameters": { + "name": { + "type": "string" + }, + "applicationInsightsName": { + "type": "string" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "tags": { + "type": "object", + "defaultValue": {} + } + }, + "resources": [ + { + "type": "Microsoft.Portal/dashboards", + "apiVersion": "2020-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "lenses": [ + { + "order": 0, + "parts": [ + { + "position": { + "x": 0, + "y": 0, + "colSpan": 2, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "id", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart", + "asset": { + "idInputName": "id", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "overview" + } + }, + { + "position": { + "x": 2, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "ProactiveDetection" + } + }, + { + "position": { + "x": 3, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + } + ], + "type": "Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:20:33.345Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 5, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-08T18:47:35.237Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "ConfigurationId", + "value": "78ce933e-e864-4b05-a27b-71fd55a6afad" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AppMapButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 0, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Usage", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 3, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:22:35.782Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + } + ], + "type": "Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Reliability", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 7, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:42:40.072Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "failures" + } + }, + { + "position": { + "x": 8, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Responsiveness\r\n", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 11, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:43:37.804Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "2a8ede4f-2bee-4b9c-aed9-2db0e8a01865", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "performance" + } + }, + { + "position": { + "x": 12, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Browser", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 15, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "MetricsExplorerJsonDefinitionId", + "value": "BrowserPerformanceTimelineMetrics" + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "createdTime": "2018-05-08T12:16:27.534Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "CurrentFilter", + "value": { + "eventTypes": [ + 4, + 1, + 3, + 5, + 2, + 6, + 13 + ], + "typeFacets": {}, + "isPermissive": false + } + }, + { + "name": "id", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "browser" + } + }, + { + "position": { + "x": 0, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "sessions/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Sessions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "users/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Users", + "color": "#7E58FF" + } + } + ], + "title": "Unique sessions and users", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "segmentationUsers" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Failed requests", + "color": "#EC008C" + } + } + ], + "title": "Failed requests", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "failures" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "requests/duration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server response time", + "color": "#00BCF2" + } + } + ], + "title": "Server response time", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "performance" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/networkDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Page load network connect time", + "color": "#7E58FF" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/processingDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Client processing time", + "color": "#44F1C8" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/sendDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Send request time", + "color": "#EB9371" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "browserTimings/receiveDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Receiving response time", + "color": "#0672F1" + } + } + ], + "title": "Average page load time breakdown", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/availabilityPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability", + "color": "#47BDF5" + } + } + ], + "title": "Average availability", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]", + "menuid": "availability" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/server", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server exceptions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "dependencies/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Dependency failures", + "color": "#7E58FF" + } + } + ], + "title": "Server exceptions and Dependency failures", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processorCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Processor time", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process CPU", + "color": "#7E58FF" + } + } + ], + "title": "Average processor and process CPU utilization", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "exceptions/browser", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Browser exceptions", + "color": "#47BDF5" + } + } + ], + "title": "Browser exceptions", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "availabilityResults/count", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability test results count", + "color": "#47BDF5" + } + } + ], + "title": "Availability test results count", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/processIOBytesPerSecond", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process IO rate", + "color": "#47BDF5" + } + } + ], + "title": "Average process I/O rate", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/components/{2}', subscription().subscriptionId, resourceGroup().name, parameters('applicationInsightsName'))]" + }, + "name": "performanceCounters/memoryAvailableBytes", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Available memory", + "color": "#47BDF5" + } + } + ], + "title": "Average available memory", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + } + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', parameters('name'))]" + ] + } + ], + "outputs": { + "connectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').ConnectionString]" + }, + "instrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', parameters('name')), '2020-02-02').InstrumentationKey]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'loganalytics')]" + ] + } + ], + "outputs": { + "applicationInsightsConnectionString": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.connectionString.value]" + }, + "applicationInsightsInstrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "applicationInsightsName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'applicationinsights'), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.id.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'loganalytics'), '2022-09-01').outputs.name.value]" + } + } + } + } + } + ], + "outputs": { + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "AZURE_LOCATION": { + "type": "string", + "value": "[parameters('location')]" + }, + "AZURE_TENANT_ID": { + "type": "string", + "value": "[tenant().tenantId]" + }, + "AZURE_AKS_CLUSTER_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.clusterName.value]" + }, + "AZURE_AKS_IDENTITY_CLIENT_ID": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.clusterIdentity.value.clientId]" + }, + "AZURE_CONTAINER_REGISTRY_ENDPOINT": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.containerRegistryLoginServer.value]" + }, + "AZURE_CONTAINER_REGISTRY_NAME": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'aks'), '2022-09-01').outputs.containerRegistryName.value]" + }, + "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'monitoring'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/ai/cognitiveservices.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/ai/cognitiveservices.bicep new file mode 100644 index 00000000..1bf5666b --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/ai/cognitiveservices.bicep @@ -0,0 +1,53 @@ +metadata description = 'Creates an Azure Cognitive Services instance.' +param name string +param location string = resourceGroup().location +param tags object = {} +@description('The custom subdomain name used to access the API. Defaults to the value of the name parameter.') +param customSubDomainName string = name +param deployments array = [] +param kind string = 'OpenAI' + +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { + name: 'S0' +} + +param allowedIpRules array = [] +param networkAcls object = empty(allowedIpRules) ? { + defaultAction: 'Allow' +} : { + ipRules: allowedIpRules + defaultAction: 'Deny' +} + +resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + customSubDomainName: customSubDomainName + publicNetworkAccess: publicNetworkAccess + networkAcls: networkAcls + } + sku: sku +} + +@batchSize(1) +resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: { + parent: account + name: deployment.name + properties: { + model: deployment.model + raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null + } + sku: contains(deployment, 'sku') ? deployment.sku : { + name: 'Standard' + capacity: 20 + } +}] + +output endpoint string = account.properties.endpoint +output id string = account.id +output name string = account.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/cosmos-account.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/cosmos-account.bicep new file mode 100644 index 00000000..6f8747f5 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/cosmos-account.bicep @@ -0,0 +1,49 @@ +metadata description = 'Creates an Azure Cosmos DB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +@allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) +param kind string + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { + name: name + kind: kind + location: location + tags: tags + properties: { + consistencyPolicy: { defaultConsistencyLevel: 'Session' } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + databaseAccountOfferType: 'Standard' + enableAutomaticFailover: false + enableMultipleWriteLocations: false + apiProperties: (kind == 'MongoDB') ? { serverVersion: '4.2' } : {} + capabilities: [ { name: 'EnableServerless' } ] + } +} + +resource cosmosConnectionString 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: cosmos.listConnectionStrings().connectionStrings[0].connectionString + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +output connectionStringKey string = connectionStringKey +output endpoint string = cosmos.properties.documentEndpoint +output id string = cosmos.id +output name string = cosmos.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/mongo/cosmos-mongo-account.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/mongo/cosmos-mongo-account.bicep new file mode 100644 index 00000000..4aafbf38 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/mongo/cosmos-mongo-account.bicep @@ -0,0 +1,23 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + connectionStringKey: connectionStringKey + keyVaultName: keyVaultName + kind: 'MongoDB' + tags: tags + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/mongo/cosmos-mongo-db.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/mongo/cosmos-mongo-db.bicep new file mode 100644 index 00000000..2a670578 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/mongo/cosmos-mongo-db.bicep @@ -0,0 +1,47 @@ +metadata description = 'Creates an Azure Cosmos DB for MongoDB account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param collections array = [] +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param keyVaultName string + +module cosmos 'cosmos-mongo-account.bicep' = { + name: 'cosmos-mongo-account' + params: { + name: accountName + location: location + keyVaultName: keyVaultName + tags: tags + connectionStringKey: connectionStringKey + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2022-08-15' = { + name: '${accountName}/${databaseName}' + tags: tags + properties: { + resource: { id: databaseName } + } + + resource list 'collections' = [for collection in collections: { + name: collection.name + properties: { + resource: { + id: collection.id + shardKey: { _id: collection.shardKey } + indexes: [ { key: { keys: [ collection.indexKey ] } } ] + } + } + }] + + dependsOn: [ + cosmos + ] +} + +output connectionStringKey string = connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-account.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-account.bicep new file mode 100644 index 00000000..8431135e --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-account.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param keyVaultName string + +module cosmos '../../cosmos/cosmos-account.bicep' = { + name: 'cosmos-account' + params: { + name: name + location: location + tags: tags + keyVaultName: keyVaultName + kind: 'GlobalDocumentDB' + } +} + +output connectionStringKey string = cosmos.outputs.connectionStringKey +output endpoint string = cosmos.outputs.endpoint +output id string = cosmos.outputs.id +output name string = cosmos.outputs.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-db.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-db.bicep new file mode 100644 index 00000000..265880dc --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-db.bicep @@ -0,0 +1,74 @@ +metadata description = 'Creates an Azure Cosmos DB for NoSQL account with a database.' +param accountName string +param databaseName string +param location string = resourceGroup().location +param tags object = {} + +param containers array = [] +param keyVaultName string +param principalIds array = [] + +module cosmos 'cosmos-sql-account.bicep' = { + name: 'cosmos-sql-account' + params: { + name: accountName + location: location + tags: tags + keyVaultName: keyVaultName + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = { + name: '${accountName}/${databaseName}' + properties: { + resource: { id: databaseName } + } + + resource list 'containers' = [for container in containers: { + name: container.name + properties: { + resource: { + id: container.id + partitionKey: { paths: [ container.partitionKey ] } + } + options: {} + } + }] + + dependsOn: [ + cosmos + ] +} + +module roleDefinition 'cosmos-sql-role-def.bicep' = { + name: 'cosmos-sql-role-definition' + params: { + accountName: accountName + } + dependsOn: [ + cosmos + database + ] +} + +// We need batchSize(1) here because sql role assignments have to be done sequentially +@batchSize(1) +module userRole 'cosmos-sql-role-assign.bicep' = [for principalId in principalIds: if (!empty(principalId)) { + name: 'cosmos-sql-user-role-${uniqueString(principalId)}' + params: { + accountName: accountName + roleDefinitionId: roleDefinition.outputs.id + principalId: principalId + } + dependsOn: [ + cosmos + database + ] +}] + +output accountId string = cosmos.outputs.id +output accountName string = cosmos.outputs.name +output connectionStringKey string = cosmos.outputs.connectionStringKey +output databaseName string = databaseName +output endpoint string = cosmos.outputs.endpoint +output roleDefinitionId string = roleDefinition.outputs.id diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-role-assign.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-role-assign.bicep new file mode 100644 index 00000000..3949efef --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-role-assign.bicep @@ -0,0 +1,19 @@ +metadata description = 'Creates a SQL role assignment under an Azure Cosmos DB account.' +param accountName string + +param roleDefinitionId string +param principalId string = '' + +resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { + parent: cosmos + name: guid(roleDefinitionId, principalId, cosmos.id) + properties: { + principalId: principalId + roleDefinitionId: roleDefinitionId + scope: cosmos.id + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-role-def.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-role-def.bicep new file mode 100644 index 00000000..778d6dc4 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/database/cosmos/sql/cosmos-sql-role-def.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates a SQL role definition under an Azure Cosmos DB account.' +param accountName string + +resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2022-08-15' = { + parent: cosmos + name: guid(cosmos.id, accountName, 'sql-role') + properties: { + assignableScopes: [ + cosmos.id + ] + permissions: [ + { + dataActions: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + ] + notDataActions: [] + } + ] + roleName: 'Reader Writer' + type: 'CustomRole' + } +} + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' existing = { + name: accountName +} + +output id string = roleDefinition.id diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/database/postgresql/flexibleserver.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/database/postgresql/flexibleserver.bicep new file mode 100644 index 00000000..7e26b1a8 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/database/postgresql/flexibleserver.bicep @@ -0,0 +1,65 @@ +metadata description = 'Creates an Azure Database for PostgreSQL - Flexible Server.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object +param storage object +param administratorLogin string +@secure() +param administratorLoginPassword string +param databaseNames array = [] +param allowAzureIPsFirewall bool = false +param allowAllIPsFirewall bool = false +param allowedSingleIPs array = [] + +// PostgreSQL version +param version string + +// Latest official version 2022-12-01 does not have Bicep types available +resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = { + location: location + tags: tags + name: name + sku: sku + properties: { + version: version + administratorLogin: administratorLogin + administratorLoginPassword: administratorLoginPassword + storage: storage + highAvailability: { + mode: 'Disabled' + } + } + + resource database 'databases' = [for name in databaseNames: { + name: name + }] + + resource firewall_all 'firewallRules' = if (allowAllIPsFirewall) { + name: 'allow-all-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '255.255.255.255' + } + } + + resource firewall_azure 'firewallRules' = if (allowAzureIPsFirewall) { + name: 'allow-all-azure-internal-IPs' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '0.0.0.0' + } + } + + resource firewall_single 'firewallRules' = [for ip in allowedSingleIPs: { + name: 'allow-single-${replace(ip, '.', '')}' + properties: { + startIpAddress: ip + endIpAddress: ip + } + }] + +} + +output POSTGRES_DOMAIN_NAME string = postgresServer.properties.fullyQualifiedDomainName diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/database/sqlserver/sqlserver.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/database/sqlserver/sqlserver.bicep new file mode 100644 index 00000000..84f2cc2c --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/database/sqlserver/sqlserver.bicep @@ -0,0 +1,130 @@ +metadata description = 'Creates an Azure SQL Server instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param appUser string = 'appUser' +param databaseName string +param keyVaultName string +param sqlAdmin string = 'sqlAdmin' +param connectionStringKey string = 'AZURE-SQL-CONNECTION-STRING' + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name + location: location + tags: tags + properties: { + version: '12.0' + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + administratorLogin: sqlAdmin + administratorLoginPassword: sqlAdminPassword + } + + resource database 'databases' = { + name: databaseName + location: location + } + + resource firewall 'firewallRules' = { + name: 'Azure Services' + properties: { + // Allow all clients + // Note: range [0.0.0.0-0.0.0.0] means "allow all Azure-hosted clients only". + // This is not sufficient, because we also want to allow direct access from developer machine, for debugging purposes. + startIpAddress: '0.0.0.1' + endIpAddress: '255.255.255.254' + } + } +} + +resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: '${name}-deployment-script' + location: location + kind: 'AzureCLI' + properties: { + azCliVersion: '2.37.0' + retentionInterval: 'PT1H' // Retain the script resource for 1 hour after it ends running + timeout: 'PT5M' // Five minutes + cleanupPreference: 'OnSuccess' + environmentVariables: [ + { + name: 'APPUSERNAME' + value: appUser + } + { + name: 'APPUSERPASSWORD' + secureValue: appUserPassword + } + { + name: 'DBNAME' + value: databaseName + } + { + name: 'DBSERVER' + value: sqlServer.properties.fullyQualifiedDomainName + } + { + name: 'SQLCMDPASSWORD' + secureValue: sqlAdminPassword + } + { + name: 'SQLADMIN' + value: sqlAdmin + } + ] + + scriptContent: ''' +wget https://github.com/microsoft/go-sqlcmd/releases/download/v0.8.1/sqlcmd-v0.8.1-linux-x64.tar.bz2 +tar x -f sqlcmd-v0.8.1-linux-x64.tar.bz2 -C . + +cat < ./initDb.sql +drop user if exists ${APPUSERNAME} +go +create user ${APPUSERNAME} with password = '${APPUSERPASSWORD}' +go +alter role db_owner add member ${APPUSERNAME} +go +SCRIPT_END + +./sqlcmd -S ${DBSERVER} -d ${DBNAME} -U ${SQLADMIN} -i ./initDb.sql + ''' + } +} + +resource sqlAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'sqlAdminPassword' + properties: { + value: sqlAdminPassword + } +} + +resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: 'appUserPassword' + properties: { + value: appUserPassword + } +} + +resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + parent: keyVault + name: connectionStringKey + properties: { + value: '${connectionString}; Password=${appUserPassword}' + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output connectionStringKey string = connectionStringKey +output databaseName string = sqlServer::database.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/gateway/apim.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/gateway/apim.bicep new file mode 100644 index 00000000..be7464f0 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/gateway/apim.bicep @@ -0,0 +1,79 @@ +metadata description = 'Creates an Azure API Management instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The email address of the owner of the service') +@minLength(1) +param publisherEmail string = 'noreply@microsoft.com' + +@description('The name of the owner of the service') +@minLength(1) +param publisherName string = 'n/a' + +@description('The pricing tier of this API Management service') +@allowed([ + 'Consumption' + 'Developer' + 'Standard' + 'Premium' +]) +param sku string = 'Consumption' + +@description('The instance size of this API Management service.') +@allowed([ 0, 1, 2 ]) +param skuCount int = 0 + +@description('Azure Application Insights Name') +param applicationInsightsName string + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': name }) + sku: { + name: sku + capacity: (sku == 'Consumption') ? 0 : ((sku == 'Developer') ? 1 : skuCount) + } + properties: { + publisherEmail: publisherEmail + publisherName: publisherName + // Custom properties are not supported for Consumption SKU + customProperties: sku == 'Consumption' ? {} : { + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_GCM_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA256': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_256_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TLS_RSA_WITH_AES_128_CBC_SHA': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TripleDes168': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Ssl30': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11': 'false' + 'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30': 'false' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2021-12-01-preview' = if (!empty(applicationInsightsName)) { + name: 'app-insights-logger' + parent: apimService + properties: { + credentials: { + instrumentationKey: applicationInsights.properties.InstrumentationKey + } + description: 'Logger to Azure Application Insights' + isBuffered: false + loggerType: 'applicationInsights' + resourceId: applicationInsights.id + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output apimServiceName string = apimService.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/aks-agent-pool.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/aks-agent-pool.bicep new file mode 100644 index 00000000..9c764358 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/aks-agent-pool.bicep @@ -0,0 +1,18 @@ +metadata description = 'Adds an agent pool to an Azure Kubernetes Service (AKS) cluster.' +param clusterName string + +@description('The agent pool name') +param name string + +@description('The agent pool configuration') +param config object + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} + +resource nodePool 'Microsoft.ContainerService/managedClusters/agentPools@2023-10-02-preview' = { + parent: aksCluster + name: name + properties: config +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/aks-managed-cluster.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/aks-managed-cluster.bicep new file mode 100644 index 00000000..de562a66 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/aks-managed-cluster.bicep @@ -0,0 +1,140 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// AAD Integration +@description('Enable Azure Active Directory integration') +param enableAad bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('Configuration of AKS add-ons') +param addOns object = {} + +@description('The log analytics workspace id used for logging & monitoring') +param workspaceId string = '' + +@description('The node pool configuration for the System agent pool') +param systemPoolConfig object + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +resource aks 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' = { + name: name + location: location + tags: tags + identity: { + type: 'SystemAssigned' + } + sku: { + name: 'Base' + tier: sku + } + properties: { + nodeResourceGroup: !empty(nodeResourceGroupName) ? nodeResourceGroupName : 'rg-mc-${name}' + kubernetesVersion: kubernetesVersion + dnsPrefix: empty(dnsPrefix) ? '${name}-dns' : dnsPrefix + enableRBAC: enableRbac + aadProfile: enableAad ? { + managed: true + enableAzureRBAC: enableAzureRbac + tenantID: aadTenantId + } : null + agentPoolProfiles: [ + systemPoolConfig + ] + networkProfile: { + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } + disableLocalAccounts: disableLocalAccounts && enableAad + addonProfiles: addOns + ingressProfile: { + webAppRouting: { + enabled: webAppRoutingAddon + } + } + } +} + +var aksDiagCategories = [ + 'cluster-autoscaler' + 'kube-controller-manager' + 'kube-audit-admin' + 'guard' +] + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'aks-diagnostics' + scope: aks + properties: { + workspaceId: workspaceId + logs: [for category in aksDiagCategories: { + category: category + enabled: true + }] + metrics: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +} + +@description('The resource name of the AKS cluster') +output clusterName string = aks.name + +@description('The AKS cluster identity') +output clusterIdentity object = { + clientId: aks.properties.identityProfile.kubeletidentity.clientId + objectId: aks.properties.identityProfile.kubeletidentity.objectId + resourceId: aks.properties.identityProfile.kubeletidentity.resourceId +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/aks.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/aks.bicep new file mode 100644 index 00000000..4a36262c --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/aks.bicep @@ -0,0 +1,268 @@ +metadata description = 'Creates an Azure Kubernetes Service (AKS) cluster with a system agent pool as well as an additional user agent pool.' +@description('The name for the AKS managed cluster') +param name string + +@description('The name for the Azure container registry (ACR)') +param containerRegistryName string + +@description('The name of the connected log analytics workspace') +param logAnalyticsName string = '' + +@description('The Azure region/location for the AKS resources') +param location string = resourceGroup().location + +@description('Custom tags to apply to the AKS resources') +param tags object = {} + +@description('AKS add-ons configuration') +param addOns object = { + azurePolicy: { + enabled: true + config: { + version: 'v2' + } + } + keyVault: { + enabled: true + config: { + enableSecretRotation: 'true' + rotationPollInterval: '2m' + } + } + openServiceMesh: { + enabled: false + config: {} + } + omsAgent: { + enabled: true + config: {} + } + applicationGateway: { + enabled: false + config: {} + } +} + +@description('The managed cluster SKU.') +@allowed([ 'Free', 'Paid', 'Standard' ]) +param sku string = 'Free' + +@description('The load balancer SKU to use for ingress into the AKS cluster') +@allowed([ 'basic', 'standard' ]) +param loadBalancerSku string = 'standard' + +@description('Network plugin used for building the Kubernetes network.') +@allowed([ 'azure', 'kubenet', 'none' ]) +param networkPlugin string = 'azure' + +@description('Network policy used for building the Kubernetes network.') +@allowed([ 'azure', 'calico' ]) +param networkPolicy string = 'azure' + +@description('The DNS prefix to associate with the AKS cluster') +param dnsPrefix string = '' + +@description('The name of the resource group for the managed resources of the AKS cluster') +param nodeResourceGroupName string = '' + +@allowed([ + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The System Pool Preset sizing') +param systemPoolType string = 'CostOptimised' + +@allowed([ + '' + 'CostOptimised' + 'Standard' + 'HighSpec' + 'Custom' +]) +@description('The User Pool Preset sizing') +param agentPoolType string = '' + +// Configure system / user agent pools +@description('Custom configuration of system node pool') +param systemPoolConfig object = {} +@description('Custom configuration of user node pool') +param agentPoolConfig object = {} + +@description('Id of the user or app to assign application roles') +param principalId string = '' + +@description('Kubernetes Version') +param kubernetesVersion string = '1.27.7' + +@description('The Tenant ID associated to the Azure Active Directory') +param aadTenantId string = tenant().tenantId + +@description('Whether RBAC is enabled for local accounts') +param enableRbac bool = true + +@description('If set to true, getting static credentials will be disabled for this cluster.') +param disableLocalAccounts bool = false + +@description('Enable RBAC using AAD') +param enableAzureRbac bool = false + +// Add-ons +@description('Whether web app routing (preview) add-on is enabled') +param webAppRoutingAddon bool = true + +// Configure AKS add-ons +var omsAgentConfig = (!empty(logAnalyticsName) && !empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? union( + addOns.omsAgent, + { + config: { + logAnalyticsWorkspaceResourceID: logAnalytics.id + } + } +) : {} + +var addOnsConfig = union( + (!empty(addOns.azurePolicy) && addOns.azurePolicy.enabled) ? { azurepolicy: addOns.azurePolicy } : {}, + (!empty(addOns.keyVault) && addOns.keyVault.enabled) ? { azureKeyvaultSecretsProvider: addOns.keyVault } : {}, + (!empty(addOns.openServiceMesh) && addOns.openServiceMesh.enabled) ? { openServiceMesh: addOns.openServiceMesh } : {}, + (!empty(addOns.omsAgent) && addOns.omsAgent.enabled) ? { omsagent: omsAgentConfig } : {}, + (!empty(addOns.applicationGateway) && addOns.applicationGateway.enabled) ? { ingressApplicationGateway: addOns.applicationGateway } : {} +) + +// Link to existing log analytics workspace when available +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' existing = if (!empty(logAnalyticsName)) { + name: logAnalyticsName +} + +var systemPoolSpec = !empty(systemPoolConfig) ? systemPoolConfig : nodePoolPresets[systemPoolType] + +// Create the primary AKS cluster resources and system node pool +module managedCluster 'aks-managed-cluster.bicep' = { + name: 'managed-cluster' + params: { + name: name + location: location + tags: tags + systemPoolConfig: union( + { name: 'npsystem', mode: 'System' }, + nodePoolBase, + systemPoolSpec + ) + nodeResourceGroupName: nodeResourceGroupName + sku: sku + dnsPrefix: dnsPrefix + kubernetesVersion: kubernetesVersion + addOns: addOnsConfig + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + enableAad: enableAzureRbac && aadTenantId != '' + disableLocalAccounts: disableLocalAccounts + aadTenantId: aadTenantId + enableRbac: enableRbac + enableAzureRbac: enableAzureRbac + webAppRoutingAddon: webAppRoutingAddon + loadBalancerSku: loadBalancerSku + networkPlugin: networkPlugin + networkPolicy: networkPolicy + } +} + +var hasAgentPool = !empty(agentPoolConfig) || !empty(agentPoolType) +var agentPoolSpec = hasAgentPool && !empty(agentPoolConfig) ? agentPoolConfig : empty(agentPoolType) ? {} : nodePoolPresets[agentPoolType] + +// Create additional user agent pool when specified +module agentPool 'aks-agent-pool.bicep' = if (hasAgentPool) { + name: 'aks-node-pool' + params: { + clusterName: managedCluster.outputs.clusterName + name: 'npuserpool' + config: union({ name: 'npuser', mode: 'User' }, nodePoolBase, agentPoolSpec) + } +} + +// Creates container registry (ACR) +module containerRegistry 'container-registry.bicep' = { + name: 'container-registry' + params: { + name: containerRegistryName + location: location + tags: tags + workspaceId: !empty(logAnalyticsName) ? logAnalytics.id : '' + } +} + +// Grant ACR Pull access from cluster managed identity to container registry +module containerRegistryAccess '../security/registry-access.bicep' = { + name: 'cluster-container-registry-access' + params: { + containerRegistryName: containerRegistry.outputs.name + principalId: managedCluster.outputs.clusterIdentity.objectId + } +} + +// Give AKS cluster access to the specified principal +module clusterAccess '../security/aks-managed-cluster-access.bicep' = if (enableAzureRbac || disableLocalAccounts) { + name: 'cluster-access' + params: { + clusterName: managedCluster.outputs.clusterName + principalId: principalId + } +} + +// Helpers for node pool configuration +var nodePoolBase = { + osType: 'Linux' + maxPods: 30 + type: 'VirtualMachineScaleSets' + upgradeSettings: { + maxSurge: '33%' + } +} + +var nodePoolPresets = { + CostOptimised: { + vmSize: 'Standard_B4ms' + count: 1 + minCount: 1 + maxCount: 3 + enableAutoScaling: true + availabilityZones: [] + } + Standard: { + vmSize: 'Standard_DS2_v2' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } + HighSpec: { + vmSize: 'Standard_D4s_v3' + count: 3 + minCount: 3 + maxCount: 5 + enableAutoScaling: true + availabilityZones: [ + '1' + '2' + '3' + ] + } +} + +// Module outputs +@description('The resource name of the AKS cluster') +output clusterName string = managedCluster.outputs.clusterName + +@description('The AKS cluster identity') +output clusterIdentity object = managedCluster.outputs.clusterIdentity + +@description('The resource name of the ACR') +output containerRegistryName string = containerRegistry.outputs.name + +@description('The login server for the container registry') +output containerRegistryLoginServer string = containerRegistry.outputs.loginServer diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/appservice-appsettings.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/appservice-appsettings.bicep new file mode 100644 index 00000000..f4b22f81 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/appservice-appsettings.bicep @@ -0,0 +1,17 @@ +metadata description = 'Updates app settings for an Azure App Service.' +@description('The name of the app service resource within the current resource group scope') +param name string + +@description('The app settings to be applied to the app service') +@secure() +param appSettings object + +resource appService 'Microsoft.Web/sites@2022-03-01' existing = { + name: name +} + +resource settings 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'appsettings' + parent: appService + properties: appSettings +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/appservice.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/appservice.bicep new file mode 100644 index 00000000..bef4d2ba --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/appservice.bicep @@ -0,0 +1,123 @@ +metadata description = 'Creates an Azure App Service in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Microsoft.Web/sites Properties +param kind string = 'app,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = false +param use32BitWorkerProcess bool = false +param ftpsState string = 'FtpsOnly' +param healthCheckPath string = '' + +resource appService 'Microsoft.Web/sites@2022-03-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + serverFarmId: appServicePlanId + siteConfig: { + linuxFxVersion: linuxFxVersion + alwaysOn: alwaysOn + ftpsState: ftpsState + minTlsVersion: '1.2' + appCommandLine: appCommandLine + numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null + minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null + use32BitWorkerProcess: use32BitWorkerProcess + functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null + healthCheckPath: healthCheckPath + cors: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: true + } + + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + + resource basicPublishingCredentialsPoliciesFtp 'basicPublishingCredentialsPolicies' = { + name: 'ftp' + properties: { + allow: false + } + } + + resource basicPublishingCredentialsPoliciesScm 'basicPublishingCredentialsPolicies' = { + name: 'scm' + properties: { + allow: false + } + } +} + +// Updates to the single Microsoft.sites/web/config resources that need to be performed sequentially +// sites/web/config 'appsettings' +module configAppSettings 'appservice-appsettings.bicep' = { + name: '${name}-appSettings' + params: { + name: appService.name + appSettings: union(appSettings, + { + SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) + ENABLE_ORYX_BUILD: string(enableOryxBuild) + }, + runtimeName == 'python' && appCommandLine == '' ? { PYTHON_ENABLE_GUNICORN_MULTIWORKERS: 'true'} : {}, + !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, + !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) + } +} + +// sites/web/config 'logs' +resource configLogs 'Microsoft.Web/sites/config@2022-03-01' = { + name: 'logs' + parent: appService + properties: { + applicationLogs: { fileSystem: { level: 'Verbose' } } + detailedErrorMessages: { enabled: true } + failedRequestsTracing: { enabled: true } + httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } + } + dependsOn: [configAppSettings] +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) { + name: keyVaultName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output identityPrincipalId string = managedIdentity ? appService.identity.principalId : '' +output name string = appService.name +output uri string = 'https://${appService.properties.defaultHostName}' diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/appserviceplan.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/appserviceplan.bicep new file mode 100644 index 00000000..2e37e041 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/appserviceplan.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param kind string = '' +param reserved bool = true +param sku object + +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + kind: kind + properties: { + reserved: reserved + } +} + +output id string = appServicePlan.id +output name string = appServicePlan.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-app-upsert.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-app-upsert.bicep new file mode 100644 index 00000000..3eec62f2 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-app-upsert.bicep @@ -0,0 +1,105 @@ +metadata description = 'Creates or updates an existing Azure Container App.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The environment name for the container apps') +param containerAppsEnvironmentName string + +@description('The number of CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('The amount of memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@allowed([ 'http', 'grpc' ]) +@description('The protocol used by Dapr to connect to the app, e.g., HTTP or gRPC') +param daprAppProtocol string = 'http' + +@description('Enable or disable Dapr for the container app') +param daprEnabled bool = false + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Specifies if the resource already exists') +param exists bool = false + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The name of the container image') +param imageName string = '' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The target port for the container') +param targetPort int = 80 + +resource existingApp 'Microsoft.App/containerApps@2023-04-01-preview' existing = if (exists) { + name: name +} + +module app 'container-app.bicep' = { + name: '${deployment().name}-update' + params: { + name: name + location: location + tags: tags + identityType: identityType + identityName: identityName + ingressEnabled: ingressEnabled + containerName: containerName + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: containerCpuCoreCount + containerMemory: containerMemory + containerMinReplicas: containerMinReplicas + containerMaxReplicas: containerMaxReplicas + daprEnabled: daprEnabled + daprAppId: daprAppId + daprAppProtocol: daprAppProtocol + secrets: secrets + external: external + env: env + imageName: !empty(imageName) ? imageName : exists ? existingApp.properties.template.containers[0].image : '' + targetPort: targetPort + serviceBinds: serviceBinds + } +} + +output defaultDomain string = app.outputs.defaultDomain +output imageName string = app.outputs.imageName +output name string = app.outputs.name +output uri string = app.outputs.uri diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-app.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-app.bicep new file mode 100644 index 00000000..3724086d --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-app.bicep @@ -0,0 +1,162 @@ +metadata description = 'Creates a container app in an Azure Container App environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Allowed origins') +param allowedOrigins array = [] + +@description('Name of the environment for container apps') +param containerAppsEnvironmentName string + +@description('CPU cores allocated to a single container instance, e.g., 0.5') +param containerCpuCoreCount string = '0.5' + +@description('The maximum number of replicas to run. Must be at least 1.') +@minValue(1) +param containerMaxReplicas int = 10 + +@description('Memory allocated to a single container instance, e.g., 1Gi') +param containerMemory string = '1.0Gi' + +@description('The minimum number of replicas to run. Must be at least 1.') +param containerMinReplicas int = 1 + +@description('The name of the container') +param containerName string = 'main' + +@description('The name of the container registry') +param containerRegistryName string = '' + +@description('The protocol used by Dapr to connect to the app, e.g., http or grpc') +@allowed([ 'http', 'grpc' ]) +param daprAppProtocol string = 'http' + +@description('The Dapr app ID') +param daprAppId string = containerName + +@description('Enable Dapr') +param daprEnabled bool = false + +@description('The environment variables for the container') +param env array = [] + +@description('Specifies if the resource ingress is exposed externally') +param external bool = true + +@description('The name of the user-assigned identity') +param identityName string = '' + +@description('The type of identity for the resource') +@allowed([ 'None', 'SystemAssigned', 'UserAssigned' ]) +param identityType string = 'None' + +@description('The name of the container image') +param imageName string = '' + +@description('Specifies if Ingress is enabled for the container app') +param ingressEnabled bool = true + +param revisionMode string = 'Single' + +@description('The secrets required for the container') +param secrets array = [] + +@description('The service binds associated with the container') +param serviceBinds array = [] + +@description('The name of the container apps add-on to use. e.g. redis') +param serviceType string = '' + +@description('The target port for the container') +param targetPort int = 80 + +resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(identityName)) { + name: identityName +} + +// Private registry support requires both an ACR name and a User Assigned managed identity +var usePrivateRegistry = !empty(identityName) && !empty(containerRegistryName) + +// Automatically set to `UserAssigned` when an `identityName` has been set +var normalizedIdentityType = !empty(identityName) ? 'UserAssigned' : identityType + +module containerRegistryAccess '../security/registry-access.bicep' = if (usePrivateRegistry) { + name: '${deployment().name}-registry-access' + params: { + containerRegistryName: containerRegistryName + principalId: usePrivateRegistry ? userIdentity.properties.principalId : '' + } +} + +resource app 'Microsoft.App/containerApps@2023-04-01-preview' = { + name: name + location: location + tags: tags + // It is critical that the identity is granted ACR pull access before the app is created + // otherwise the container app will throw a provision error + // This also forces us to use an user assigned managed identity since there would no way to + // provide the system assigned identity with the ACR pull access before the app is created + dependsOn: usePrivateRegistry ? [ containerRegistryAccess ] : [] + identity: { + type: normalizedIdentityType + userAssignedIdentities: !empty(identityName) && normalizedIdentityType == 'UserAssigned' ? { '${userIdentity.id}': {} } : null + } + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + activeRevisionsMode: revisionMode + ingress: ingressEnabled ? { + external: external + targetPort: targetPort + transport: 'auto' + corsPolicy: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } : null + dapr: daprEnabled ? { + enabled: true + appId: daprAppId + appProtocol: daprAppProtocol + appPort: ingressEnabled ? targetPort : 0 + } : { enabled: false } + secrets: secrets + service: !empty(serviceType) ? { type: serviceType } : null + registries: usePrivateRegistry ? [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userIdentity.id + } + ] : [] + } + template: { + serviceBinds: !empty(serviceBinds) ? serviceBinds : null + containers: [ + { + image: !empty(imageName) ? imageName : 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: containerName + env: env + resources: { + cpu: json(containerCpuCoreCount) + memory: containerMemory + } + } + ] + scale: { + minReplicas: containerMinReplicas + maxReplicas: containerMaxReplicas + } + } + } +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' existing = { + name: containerAppsEnvironmentName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output identityPrincipalId string = normalizedIdentityType == 'None' ? '' : (empty(identityName) ? app.identity.principalId : userIdentity.properties.principalId) +output imageName string = imageName +output name string = app.name +output serviceBind object = !empty(serviceType) ? { serviceId: app.id, name: name } : {} +output uri string = ingressEnabled ? 'https://${app.properties.configuration.ingress.fqdn}' : '' diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-apps-environment.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-apps-environment.bicep new file mode 100644 index 00000000..8633ba48 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-apps-environment.bicep @@ -0,0 +1,41 @@ +metadata description = 'Creates an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the Application Insights resource') +param applicationInsightsName string = '' + +@description('Specifies if Dapr is enabled') +param daprEnabled bool = false + +@description('Name of the Log Analytics workspace') +param logAnalyticsWorkspaceName string + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-04-01-preview' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: logAnalyticsWorkspaceName +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { + name: applicationInsightsName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output id string = containerAppsEnvironment.id +output name string = containerAppsEnvironment.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-apps.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-apps.bicep new file mode 100644 index 00000000..1c656e28 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-apps.bicep @@ -0,0 +1,40 @@ +metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param containerRegistryName string +param containerRegistryResourceGroupName string = '' +param containerRegistryAdminUserEnabled bool = false +param logAnalyticsWorkspaceName string +param applicationInsightsName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${name}-container-apps-environment' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + logAnalyticsWorkspaceName: logAnalyticsWorkspaceName + applicationInsightsName: applicationInsightsName + } +} + +module containerRegistry 'container-registry.bicep' = { + name: '${name}-container-registry' + scope: !empty(containerRegistryResourceGroupName) ? resourceGroup(containerRegistryResourceGroupName) : resourceGroup() + params: { + name: containerRegistryName + location: location + adminUserEnabled: containerRegistryAdminUserEnabled + tags: tags + } +} + +output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output environmentName string = containerAppsEnvironment.outputs.name +output environmentId string = containerAppsEnvironment.outputs.id + +output registryLoginServer string = containerRegistry.outputs.loginServer +output registryName string = containerRegistry.outputs.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-registry.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-registry.bicep new file mode 100644 index 00000000..9c64531b --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/container-registry.bicep @@ -0,0 +1,83 @@ +metadata description = 'Creates an Azure Container Registry.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('Indicates whether admin user is enabled') +param adminUserEnabled bool = false + +@description('Indicates whether anonymous pull is enabled') +param anonymousPullEnabled bool = false + +@description('Indicates whether data endpoint is enabled') +param dataEndpointEnabled bool = false + +@description('Encryption settings') +param encryption object = { + status: 'disabled' +} + +@description('Options for bypassing network rules') +param networkRuleBypassOptions string = 'AzureServices' + +@description('Public network access setting') +param publicNetworkAccess string = 'Enabled' + +@description('SKU settings') +param sku object = { + name: 'Basic' +} + +@description('Zone redundancy setting') +param zoneRedundancy string = 'Disabled' + +@description('The log analytics workspace ID used for logging and monitoring') +param workspaceId string = '' + +// 2022-02-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { + name: name + location: location + tags: tags + sku: sku + properties: { + adminUserEnabled: adminUserEnabled + anonymousPullEnabled: anonymousPullEnabled + dataEndpointEnabled: dataEndpointEnabled + encryption: encryption + networkRuleBypassOptions: networkRuleBypassOptions + publicNetworkAccess: publicNetworkAccess + zoneRedundancy: zoneRedundancy + } +} + +// TODO: Update diagnostics to be its own module +// Blocking issue: https://github.com/Azure/bicep/issues/622 +// Unable to pass in a `resource` scope or unable to use string interpolation in resource types +resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: 'registry-diagnostics' + scope: containerRegistry + properties: { + workspaceId: workspaceId + logs: [ + { + category: 'ContainerRegistryRepositoryEvents' + enabled: true + } + { + category: 'ContainerRegistryLoginEvents' + enabled: true + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + timeGrain: 'PT1M' + } + ] + } +} + +output loginServer string = containerRegistry.properties.loginServer +output name string = containerRegistry.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/functions.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/functions.bicep new file mode 100644 index 00000000..7070a2c6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/functions.bicep @@ -0,0 +1,86 @@ +metadata description = 'Creates an Azure Function in an existing Azure App Service plan.' +param name string +param location string = resourceGroup().location +param tags object = {} + +// Reference Properties +param applicationInsightsName string = '' +param appServicePlanId string +param keyVaultName string = '' +param managedIdentity bool = !empty(keyVaultName) +param storageAccountName string + +// Runtime Properties +@allowed([ + 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' +]) +param runtimeName string +param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' +param runtimeVersion string + +// Function Settings +@allowed([ + '~4', '~3', '~2', '~1' +]) +param extensionVersion string = '~4' + +// Microsoft.Web/sites Properties +param kind string = 'functionapp,linux' + +// Microsoft.Web/sites/config +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +@secure() +param appSettings object = {} +param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') +param functionAppScaleLimit int = -1 +param linuxFxVersion string = runtimeNameAndVersion +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = true +param use32BitWorkerProcess bool = false +param healthCheckPath string = '' + +module functions 'appservice.bicep' = { + name: '${name}-functions' + params: { + name: name + location: location + tags: tags + allowedOrigins: allowedOrigins + alwaysOn: alwaysOn + appCommandLine: appCommandLine + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: union(appSettings, { + AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${storage.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' + FUNCTIONS_EXTENSION_VERSION: extensionVersion + FUNCTIONS_WORKER_RUNTIME: runtimeName + }) + clientAffinityEnabled: clientAffinityEnabled + enableOryxBuild: enableOryxBuild + functionAppScaleLimit: functionAppScaleLimit + healthCheckPath: healthCheckPath + keyVaultName: keyVaultName + kind: kind + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + minimumElasticInstanceCount: minimumElasticInstanceCount + numberOfWorkers: numberOfWorkers + runtimeName: runtimeName + runtimeVersion: runtimeVersion + runtimeNameAndVersion: runtimeNameAndVersion + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + use32BitWorkerProcess: use32BitWorkerProcess + } +} + +resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { + name: storageAccountName +} + +output identityPrincipalId string = managedIdentity ? functions.outputs.identityPrincipalId : '' +output name string = functions.outputs.name +output uri string = functions.outputs.uri diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/host/staticwebapp.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/host/staticwebapp.bicep new file mode 100644 index 00000000..cedaf906 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/host/staticwebapp.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates an Azure Static Web Apps instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'Free' + tier: 'Free' +} + +resource web 'Microsoft.Web/staticSites@2022-03-01' = { + name: name + location: location + tags: tags + sku: sku + properties: { + provider: 'Custom' + } +} + +output name string = web.name +output uri string = 'https://${web.properties.defaultHostname}' diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/applicationinsights-dashboard.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/applicationinsights-dashboard.bicep new file mode 100644 index 00000000..d082e668 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/applicationinsights-dashboard.bicep @@ -0,0 +1,1236 @@ +metadata description = 'Creates a dashboard for an Application Insights instance.' +param name string +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: name + location: location + tags: tags + properties: { + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsights.name + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Insights/components/${applicationInsights.name}' + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: applicationInsightsName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/applicationinsights.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/applicationinsights.bicep new file mode 100644 index 00000000..4b4d01e3 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/applicationinsights.bicep @@ -0,0 +1,30 @@ +metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' +param name string +param dashboardName string = '' +param location string = resourceGroup().location +param tags object = {} +param logAnalyticsWorkspaceId string + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { + name: 'application-insights-dashboard' + params: { + name: dashboardName + location: location + applicationInsightsName: applicationInsights.name + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/loganalytics.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/loganalytics.bicep new file mode 100644 index 00000000..33f9dc29 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/loganalytics.bicep @@ -0,0 +1,22 @@ +metadata description = 'Creates a Log Analytics workspace.' +param name string +param location string = resourceGroup().location +param tags object = {} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: name + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/monitoring.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/monitoring.bicep new file mode 100644 index 00000000..6bb05b0b --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/monitor/monitoring.bicep @@ -0,0 +1,32 @@ +metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string = '' +param location string = resourceGroup().location +param tags object = {} + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + name: logAnalyticsName + location: location + tags: tags + } +} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name +output logAnalyticsWorkspaceId string = logAnalytics.outputs.id +output logAnalyticsWorkspaceName string = logAnalytics.outputs.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/networking/cdn-endpoint.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/networking/cdn-endpoint.bicep new file mode 100644 index 00000000..5e8ab695 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/networking/cdn-endpoint.bicep @@ -0,0 +1,52 @@ +metadata description = 'Adds an endpoint to an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The name of the CDN profile resource') +@minLength(1) +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('The origin URL for the endpoint') +@minLength(1) +param originUrl string + +resource endpoint 'Microsoft.Cdn/profiles/endpoints@2022-05-01-preview' = { + parent: cdnProfile + name: name + location: location + tags: tags + properties: { + originHostHeader: originUrl + isHttpAllowed: false + isHttpsAllowed: true + queryStringCachingBehavior: 'UseQueryString' + optimizationType: 'GeneralWebDelivery' + origins: [ + { + name: replace(originUrl, '.', '-') + properties: { + hostName: originUrl + originHostHeader: originUrl + priority: 1 + weight: 1000 + enabled: true + } + } + ] + deliveryPolicy: { + rules: deliveryPolicyRules + } + } +} + +resource cdnProfile 'Microsoft.Cdn/profiles@2022-05-01-preview' existing = { + name: cdnProfileName +} + +output id string = endpoint.id +output name string = endpoint.name +output uri string = 'https://${endpoint.properties.hostName}' diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/networking/cdn-profile.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/networking/cdn-profile.bicep new file mode 100644 index 00000000..27669ee2 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/networking/cdn-profile.bicep @@ -0,0 +1,34 @@ +metadata description = 'Creates an Azure CDN profile.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@description('The pricing tier of this CDN profile') +@allowed([ + 'Custom_Verizon' + 'Premium_AzureFrontDoor' + 'Premium_Verizon' + 'StandardPlus_955BandWidth_ChinaCdn' + 'StandardPlus_AvgBandWidth_ChinaCdn' + 'StandardPlus_ChinaCdn' + 'Standard_955BandWidth_ChinaCdn' + 'Standard_Akamai' + 'Standard_AvgBandWidth_ChinaCdn' + 'Standard_AzureFrontDoor' + 'Standard_ChinaCdn' + 'Standard_Microsoft' + 'Standard_Verizon' +]) +param sku string = 'Standard_Microsoft' + +resource profile 'Microsoft.Cdn/profiles@2022-05-01-preview' = { + name: name + location: location + tags: tags + sku: { + name: sku + } +} + +output id string = profile.id +output name string = profile.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/networking/cdn.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/networking/cdn.bicep new file mode 100644 index 00000000..de98a1f9 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/networking/cdn.bicep @@ -0,0 +1,42 @@ +metadata description = 'Creates an Azure CDN profile with a single endpoint.' +param location string = resourceGroup().location +param tags object = {} + +@description('Name of the CDN endpoint resource') +param cdnEndpointName string + +@description('Name of the CDN profile resource') +param cdnProfileName string + +@description('Delivery policy rules') +param deliveryPolicyRules array = [] + +@description('Origin URL for the CDN endpoint') +param originUrl string + +module cdnProfile 'cdn-profile.bicep' = { + name: 'cdn-profile' + params: { + name: cdnProfileName + location: location + tags: tags + } +} + +module cdnEndpoint 'cdn-endpoint.bicep' = { + name: 'cdn-endpoint' + params: { + name: cdnEndpointName + location: location + tags: tags + cdnProfileName: cdnProfile.outputs.name + originUrl: originUrl + deliveryPolicyRules: deliveryPolicyRules + } +} + +output endpointName string = cdnEndpoint.outputs.name +output endpointId string = cdnEndpoint.outputs.id +output profileName string = cdnProfile.outputs.name +output profileId string = cdnProfile.outputs.id +output uri string = cdnEndpoint.outputs.uri diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/search/search-services.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/search/search-services.bicep new file mode 100644 index 00000000..d9c619a9 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/search/search-services.bicep @@ -0,0 +1,68 @@ +metadata description = 'Creates an Azure AI Search instance.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param sku object = { + name: 'standard' +} + +param authOptions object = {} +param disableLocalAuth bool = false +param disabledDataExfiltrationOptions array = [] +param encryptionWithCmk object = { + enforcement: 'Unspecified' +} +@allowed([ + 'default' + 'highDensity' +]) +param hostingMode string = 'default' +param networkRuleSet object = { + bypass: 'None' + ipRules: [] +} +param partitionCount int = 1 +@allowed([ + 'enabled' + 'disabled' +]) +param publicNetworkAccess string = 'enabled' +param replicaCount int = 1 +@allowed([ + 'disabled' + 'free' + 'standard' +]) +param semanticSearch string = 'disabled' + +var searchIdentityProvider = (sku.name == 'free') ? null : { + type: 'SystemAssigned' +} + +resource search 'Microsoft.Search/searchServices@2021-04-01-preview' = { + name: name + location: location + tags: tags + // The free tier does not support managed identity + identity: searchIdentityProvider + properties: { + authOptions: authOptions + disableLocalAuth: disableLocalAuth + disabledDataExfiltrationOptions: disabledDataExfiltrationOptions + encryptionWithCmk: encryptionWithCmk + hostingMode: hostingMode + networkRuleSet: networkRuleSet + partitionCount: partitionCount + publicNetworkAccess: publicNetworkAccess + replicaCount: replicaCount + semanticSearch: semanticSearch + } + sku: sku +} + +output id string = search.id +output endpoint string = 'https://${name}.search.windows.net/' +output name string = search.name +output principalId string = !empty(searchIdentityProvider) ? search.identity.principalId : '' + diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/security/aks-managed-cluster-access.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/security/aks-managed-cluster-access.bicep new file mode 100644 index 00000000..dec984e8 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/security/aks-managed-cluster-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns RBAC role to the specified AKS cluster and principal.' +param clusterName string +param principalId string + +var aksClusterAdminRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b') + +resource aksRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: aksCluster // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, aksClusterAdminRole) + properties: { + roleDefinitionId: aksClusterAdminRole + principalType: 'User' + principalId: principalId + } +} + +resource aksCluster 'Microsoft.ContainerService/managedClusters@2023-10-02-preview' existing = { + name: clusterName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/security/keyvault-access.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/security/keyvault-access.bicep new file mode 100644 index 00000000..316775f2 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/security/keyvault-access.bicep @@ -0,0 +1,22 @@ +metadata description = 'Assigns an Azure Key Vault access policy.' +param name string = 'add' + +param keyVaultName string +param permissions object = { secrets: [ 'get', 'list' ] } +param principalId string + +resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = { + parent: keyVault + name: name + properties: { + accessPolicies: [ { + objectId: principalId + tenantId: subscription().tenantId + permissions: permissions + } ] + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/security/keyvault-secret.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/security/keyvault-secret.bicep new file mode 100644 index 00000000..7441b296 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/security/keyvault-secret.bicep @@ -0,0 +1,31 @@ +metadata description = 'Creates or updates a secret in an Azure Key Vault.' +param name string +param tags object = {} +param keyVaultName string +param contentType string = 'string' +@description('The value of the secret. Provide only derived values like blob storage access, but do not hard code any secrets in your templates') +@secure() +param secretValue string + +param enabled bool = true +param exp int = 0 +param nbf int = 0 + +resource keyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { + name: name + tags: tags + parent: keyVault + properties: { + attributes: { + enabled: enabled + exp: exp + nbf: nbf + } + contentType: contentType + value: secretValue + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/security/keyvault.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/security/keyvault.bicep new file mode 100644 index 00000000..314a1db6 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/security/keyvault.bicep @@ -0,0 +1,26 @@ +metadata description = 'Creates an Azure Key Vault.' +param name string +param location string = resourceGroup().location +param tags object = {} + +param principalId string = '' + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: name + location: location + tags: tags + properties: { + tenantId: subscription().tenantId + sku: { family: 'A', name: 'standard' } + accessPolicies: !empty(principalId) ? [ + { + objectId: principalId + permissions: { secrets: [ 'get', 'list' ] } + tenantId: subscription().tenantId + } + ] : [] + } +} + +output endpoint string = keyVault.properties.vaultUri +output name string = keyVault.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/security/registry-access.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/security/registry-access.bicep new file mode 100644 index 00000000..5335efab --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/security/registry-access.bicep @@ -0,0 +1,19 @@ +metadata description = 'Assigns ACR Pull permissions to access an Azure Container Registry.' +param containerRegistryName string +param principalId string + +var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + +resource aksAcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: containerRegistry // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, acrPullRole) + properties: { + roleDefinitionId: acrPullRole + principalType: 'ServicePrincipal' + principalId: principalId + } +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { + name: containerRegistryName +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/security/role.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/security/role.bicep new file mode 100644 index 00000000..0b30cfd3 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/security/role.bicep @@ -0,0 +1,21 @@ +metadata description = 'Creates a role assignment for a service principal.' +param principalId string + +@allowed([ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' +]) +param principalType string = 'ServicePrincipal' +param roleDefinitionId string + +resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) + properties: { + principalId: principalId + principalType: principalType + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) + } +} diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/storage/storage-account.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/storage/storage-account.bicep new file mode 100644 index 00000000..4b6febbe --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/storage/storage-account.bicep @@ -0,0 +1,64 @@ +metadata description = 'Creates an Azure storage account.' +param name string +param location string = resourceGroup().location +param tags object = {} + +@allowed([ + 'Cool' + 'Hot' + 'Premium' ]) +param accessTier string = 'Hot' +param allowBlobPublicAccess bool = true +param allowCrossTenantReplication bool = true +param allowSharedKeyAccess bool = true +param containers array = [] +param defaultToOAuthAuthentication bool = false +param deleteRetentionPolicy object = {} +@allowed([ 'AzureDnsZone', 'Standard' ]) +param dnsEndpointType string = 'Standard' +param kind string = 'StorageV2' +param minimumTlsVersion string = 'TLS1_2' +param supportsHttpsTrafficOnly bool = true +param networkAcls object = { + bypass: 'AzureServices' + defaultAction: 'Allow' +} +@allowed([ 'Enabled', 'Disabled' ]) +param publicNetworkAccess string = 'Enabled' +param sku object = { name: 'Standard_LRS' } + +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { + name: name + location: location + tags: tags + kind: kind + sku: sku + properties: { + accessTier: accessTier + allowBlobPublicAccess: allowBlobPublicAccess + allowCrossTenantReplication: allowCrossTenantReplication + allowSharedKeyAccess: allowSharedKeyAccess + defaultToOAuthAuthentication: defaultToOAuthAuthentication + dnsEndpointType: dnsEndpointType + minimumTlsVersion: minimumTlsVersion + networkAcls: networkAcls + publicNetworkAccess: publicNetworkAccess + supportsHttpsTrafficOnly: supportsHttpsTrafficOnly + } + + resource blobServices 'blobServices' = if (!empty(containers)) { + name: 'default' + properties: { + deleteRetentionPolicy: deleteRetentionPolicy + } + resource container 'containers' = [for container in containers: { + name: container.name + properties: { + publicAccess: contains(container, 'publicAccess') ? container.publicAccess : 'None' + } + }] + } +} + +output name string = storage.name +output primaryEndpoints object = storage.properties.primaryEndpoints diff --git a/Environments/Contoso-Base-Shared-AKS-Test/core/testing/loadtesting.bicep b/Environments/Contoso-Base-Shared-AKS-Test/core/testing/loadtesting.bicep new file mode 100644 index 00000000..46781086 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/core/testing/loadtesting.bicep @@ -0,0 +1,15 @@ +param name string +param location string = resourceGroup().location +param managedIdentity bool = false +param tags object = {} + +resource loadTest 'Microsoft.LoadTestService/loadTests@2022-12-01' = { + name: name + location: location + tags: tags + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } + properties: { + } +} + +output loadTestingName string = loadTest.name diff --git a/Environments/Contoso-Base-Shared-AKS-Test/main.bicep b/Environments/Contoso-Base-Shared-AKS-Test/main.bicep new file mode 100644 index 00000000..c283ef31 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/main.bicep @@ -0,0 +1,55 @@ +@minLength(1) +@maxLength(64) +@description('Name of the the environment which is used to generate a short unique hash used in all resources.') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string = resourceGroup().location + +@description('The resource name of the AKS cluster') +param clusterName string = '' + +@description('The resource name of the Container Registry (ACR)') +param containerRegistryName string = '' + +param applicationInsightsDashboardName string = '' +param applicationInsightsName string = '' +param logAnalyticsName string = '' + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +// The AKS cluster to host applications +module aks './core/host/aks.bicep' = { + name: 'aks' + params: { + location: location + name: !empty(clusterName) ? clusterName : '${abbrs.containerServiceManagedClusters}${resourceToken}' + containerRegistryName: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + logAnalyticsName: monitoring.outputs.logAnalyticsWorkspaceName + } +} + +// Monitor application with Azure Monitor +module monitoring './core/monitor/monitoring.bicep' = { + name: 'monitoring' + params: { + location: location + tags: tags + logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}' + } +} + +// App outputs +output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId +output AZURE_AKS_CLUSTER_NAME string = aks.outputs.clusterName +output AZURE_AKS_IDENTITY_CLIENT_ID string = aks.outputs.clusterIdentity.clientId +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = aks.outputs.containerRegistryLoginServer +output AZURE_CONTAINER_REGISTRY_NAME string = aks.outputs.containerRegistryName +output REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString diff --git a/Environments/Contoso-Base-Shared-AKS-Test/main.parameters.json b/Environments/Contoso-Base-Shared-AKS-Test/main.parameters.json new file mode 100644 index 00000000..67ad8524 --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/main.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "principalId": { + "value": "${AZURE_PRINCIPAL_ID}" + } + } +} \ No newline at end of file diff --git a/Environments/Contoso-Base-Shared-AKS-Test/manifest.yaml b/Environments/Contoso-Base-Shared-AKS-Test/manifest.yaml new file mode 100644 index 00000000..2ceb185f --- /dev/null +++ b/Environments/Contoso-Base-Shared-AKS-Test/manifest.yaml @@ -0,0 +1,21 @@ +name: Contoso-Base-Shared-AKS-Test +version: 1.0.0 +summary: Base shared environment for app development in AKS +description: Deploys base shared env (Container environment and monitoring) for app development in AKS +runner: ARM +templatePath: azuredeploy.json + +parameters: +- id: "environmentName" + name: "Environment Name (e.g. testenv)" + description: "Name of the Environment" + type: string + required: true + +- id: "location" + name: "Region (e.g. eastus)" + description: "Location of the resources" + type: string + required: true + +