diff --git a/templates/todo/api/python/todo/models.py b/templates/todo/api/python/todo/models.py index 7710395dc9d..e6933bafb15 100644 --- a/templates/todo/api/python/todo/models.py +++ b/templates/todo/api/python/todo/models.py @@ -20,11 +20,12 @@ def __init__(self, *args, **kwargs): credential = DefaultAzureCredential() keyvault_client = SecretClient(self.AZURE_KEY_VAULT_ENDPOINT, credential) for secret in keyvault_client.list_properties_of_secrets(): - setattr( - self, - keyvault_name_as_attr(secret.name), - keyvault_client.get_secret(secret.name).value, - ) + if secret.name == "AZURE-COSMOS-CONNECTION-STRING": + setattr( + self, + keyvault_name_as_attr(secret.name), + keyvault_client.get_secret(secret.name).value, + ) AZURE_COSMOS_CONNECTION_STRING: str = "" AZURE_COSMOS_DATABASE_NAME: str = "Todo" diff --git a/templates/todo/common/infra/bicep/app/apim-api-settings.bicep b/templates/todo/common/infra/bicep/app/apim-api-settings.bicep new file mode 100644 index 00000000000..3b3f342861c --- /dev/null +++ b/templates/todo/common/infra/bicep/app/apim-api-settings.bicep @@ -0,0 +1,99 @@ +@description('Resource name for the existing apim service') +param name string + +@description('Resource name to uniquely identify this API within the API Management service instance') +@minLength(1) +param apiName 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('Resource name for the existing applicationInsights service') +param applicationInsightsName string + +@description('Resource name for backend Web App or Function App') +param apiAppName string = '' + +// 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 apiDiagnostics 'Microsoft.ApiManagement/service/apis/diagnostics@2021-12-01-preview' = { + name: 'applicationinsights' + parent: apimService::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 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' = 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 +} + +resource apimService 'Microsoft.ApiManagement/service@2021-08-01' existing = { + name: name + + resource restApi 'apis@2021-12-01-preview' existing = { + name: apiName + } +} + +output SERVICE_API_URI string = '${apimService.properties.gatewayUrl}/${apiPath}' diff --git a/templates/todo/common/infra/bicep/app/applicationinsights-dashboard.bicep b/templates/todo/common/infra/bicep/app/applicationinsights-dashboard.bicep new file mode 100644 index 00000000000..d082e668ed9 --- /dev/null +++ b/templates/todo/common/infra/bicep/app/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/templates/todo/projects/python-mongo-aca/.repo/bicep/infra/main.bicep b/templates/todo/projects/python-mongo-aca/.repo/bicep/infra/main.bicep index 96945b54d09..cb9cab36e68 100644 --- a/templates/todo/projects/python-mongo-aca/.repo/bicep/infra/main.bicep +++ b/templates/todo/projects/python-mongo-aca/.repo/bicep/infra/main.bicep @@ -25,19 +25,48 @@ param logAnalyticsName string = '' param resourceGroupName string = '' param webContainerAppName string = '' param apimServiceName string = '' -param apiAppExists bool = false -param webAppExists bool = false +param connectionStringKey string = 'AZURE-COSMOS-CONNECTION-STRING' +param collections array = [ + { + name: 'TodoList' + id: 'TodoList' + shardKey: {keys: [ + 'Hash' + ]} + indexes: [ + { + key: { + keys: [ + '_id' + ] + } + } + ] + } + { + name: 'TodoItem' + id: 'TodoItem' + shardKey: {keys: [ + 'Hash' + ]} + indexes: [ + { + key: { + keys: [ + '_id' + ] + } + } + ] + } +] @description('Flag to use Azure API Management to mediate the calls between the Web frontend and the backend API') param useAPIM bool = false -@description('API Management SKU to use if APIM is enabled') -param apimSku string = 'Consumption' - @description('Hostname suffix for container registry. Set when deploying to sovereign clouds') param containerRegistryHostSuffix string = 'azurecr.io' - @description('Id of the user or app to assign application roles') param principalId string = '' @@ -48,7 +77,8 @@ var abbrs = loadJsonContent('../../../../../../common/infra/bicep/abbreviations. 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}' +var corsAcaUrl = 'https://${apiContainerAppNameOrDefault}.${containerAppsEnvironment.outputs.defaultDomain}' +var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') // Organize resources in a resource group resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { @@ -57,147 +87,299 @@ resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { tags: tags } -// Container apps host (including container registry) -module containerApps '../../../../../../common/infra/bicep/core/host/container-apps.bicep' = { - name: 'container-apps' +// Container registry +module containerRegistry 'br/public:avm/res/container-registry/registry:0.1.1' = { + name: 'registry' + scope: rg + params: { + name: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + location: location + acrAdminUserEnabled: true + tags: tags + publicNetworkAccess: 'Enabled' + roleAssignments:[ + { + principalId: webIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: acrPullRole + } + { + principalId: apiIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: acrPullRole + } + ] + } +} + +//Container apps environment +module containerAppsEnvironment 'br/public:avm/res/app/managed-environment:0.4.5' = { + name: 'containerAppsEnvironment' scope: rg params: { - name: 'app' + logAnalyticsWorkspaceResourceId: loganalytics.outputs.resourceId + name: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' location: location + zoneRedundant: false 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 + } +} + +//the managed identity for web frontend +module webIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.1' = { + name: 'webIdentity' + scope: rg + params: { + name: '${abbrs.managedIdentityUserAssignedIdentities}web-${resourceToken}' + location: location } } // Web frontend -module web '../../../../../common/infra/bicep/app/web-container-app.bicep' = { +module web 'br/public:avm/res/app/container-app:0.2.0' = { name: 'web' scope: rg params: { name: !empty(webContainerAppName) ? webContainerAppName : '${abbrs.appContainerApps}web-${resourceToken}' + containers: [ + { + image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: 'simple-hello-world-container' + resources: { + cpu: json('0.5') + memory: '1.0Gi' + } + } + ] + managedIdentities:{ + systemAssigned: false + userAssignedResourceIds: [webIdentity.outputs.resourceId] + } + registries:[ + { + server: '${containerRegistry.outputs.name}.${containerRegistryHostSuffix}' + identity: webIdentity.outputs.resourceId + } + ] + dapr: { + enabled: true + appId: 'main' + appProtocol: 'http' + appPort: 80 + } + environmentId: containerAppsEnvironment.outputs.resourceId + location: location + tags: union(tags, { 'azd-service-name': 'web' }) + } +} + +//the managed identity for api backend +module apiIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.1' = { + name: 'apiIdentity' + scope: rg + params: { + name: '${abbrs.managedIdentityUserAssignedIdentities}api-${resourceToken}' location: location - tags: tags - identityName: '${abbrs.managedIdentityUserAssignedIdentities}web-${resourceToken}' - containerAppsEnvironmentName: containerApps.outputs.environmentName - containerRegistryName: containerApps.outputs.registryName - containerRegistryHostSuffix: containerRegistryHostSuffix - exists: webAppExists } } // Api backend -module api '../../../../../common/infra/bicep/app/api-container-app.bicep' = { +module api 'br/public:avm/res/app/container-app:0.2.0' = { name: 'api' scope: rg params: { name: !empty(apiContainerAppName) ? apiContainerAppName : '${abbrs.appContainerApps}api-${resourceToken}' + containers: [ + { + image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: 'simple-hello-world-container' + resources: { + cpu: json('1.0') + memory: '2.0Gi' + } + env: [ + { + name: 'AZURE_CLIENT_ID' + value: apiIdentity.outputs.clientId + } + { + name: 'AZURE_KEY_VAULT_ENDPOINT' + value: keyVault.outputs.uri + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.outputs.connectionString + } + { + name: 'API_ALLOW_ORIGINS' + value: corsAcaUrl + } + ] + } + ] + managedIdentities:{ + systemAssigned: false + userAssignedResourceIds: [apiIdentity.outputs.resourceId] + } + registries:[ + { + server: '${containerRegistry.outputs.name}.${containerRegistryHostSuffix}' + identity: apiIdentity.outputs.resourceId + } + ] + environmentId: containerAppsEnvironment.outputs.resourceId + ingressTargetPort: 3100 location: location - tags: tags - identityName: '${abbrs.managedIdentityUserAssignedIdentities}api-${resourceToken}' - applicationInsightsName: monitoring.outputs.applicationInsightsName - containerAppsEnvironmentName: containerApps.outputs.environmentName - containerRegistryName: containerApps.outputs.registryName - containerRegistryHostSuffix: containerRegistryHostSuffix - keyVaultName: keyVault.outputs.name - corsAcaUrl: corsAcaUrl - exists: apiAppExists + tags: union(tags, { 'azd-service-name': 'api' }) } } // The application database -module cosmos '../../../../../common/infra/bicep/app/cosmos-mongo-db.bicep' = { +module cosmos 'br/public:avm/res/document-db/database-account:0.4.0' = { name: 'cosmos' scope: rg params: { - accountName: !empty(cosmosAccountName) ? cosmosAccountName : '${abbrs.documentDBDatabaseAccounts}${resourceToken}' - databaseName: cosmosDatabaseName + locations: [ + { + failoverPriority: 0 + isZoneRedundant: false + locationName: location + } + ] + name: !empty(cosmosAccountName) ? cosmosAccountName : '${abbrs.documentDBDatabaseAccounts}${resourceToken}' location: location - tags: tags - keyVaultName: keyVault.outputs.name + mongodbDatabases: [ + { + name: 'Todo' + tags: tags + collections: collections + } + ] + secretsKeyVault: { + keyVaultName: keyVault.outputs.name + primaryWriteConnectionStringSecretName: connectionStringKey + } } } // Store secrets in a keyvault -module keyVault '../../../../../../common/infra/bicep/core/security/keyvault.bicep' = { +module keyVault 'br/public:avm/res/key-vault/vault:0.5.1' = { name: 'keyvault' scope: rg params: { name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' location: location tags: tags - principalId: principalId + enableRbacAuthorization: false + accessPolicies: [ + { + objectId: principalId + permissions: { + secrets: [ 'get', 'list' ] + } + } + { + objectId: apiIdentity.outputs.principalId + permissions: { + secrets: [ 'get', 'list' ] + } + } + ] } } -// Monitor application with Azure Monitor -module monitoring '../../../../../../common/infra/bicep/core/monitor/monitoring.bicep' = { - name: 'monitoring' +// Monitor application with Azure loganalytics +module loganalytics 'br/public:avm/res/operational-insights/workspace:0.3.4' = { + name: 'loganalytics' scope: rg params: { + name: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' 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}' + } +} + +// Monitor application with Azure applicationInsights +module applicationInsights 'br/public:avm/res/insights/component:0.3.0' = { + name: 'applicationInsights' + scope: rg + params: { + name: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + workspaceResourceId: loganalytics.outputs.resourceId + location: location + } +} + +//Monitor application with Azure applicationInsightsDashboard +module applicationInsightsDashboard '../../../../../common/infra/bicep/app/applicationinsights-dashboard.bicep' = { + name: 'application-insights-dashboard' + scope: rg + params: { + name: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}' + location: location + applicationInsightsName: applicationInsights.outputs.name } } // Creates Azure API Management (APIM) service to mediate the requests between the frontend and the backend API -module apim '../../../../../../common/infra/bicep/core/gateway/apim.bicep' = if (useAPIM) { +module apim 'br/public:avm/res/api-management/service:0.1.3' = if (useAPIM) { name: 'apim-deployment' scope: rg params: { name: !empty(apimServiceName) ? apimServiceName : '${abbrs.apiManagementService}${resourceToken}' - sku: apimSku + publisherEmail: 'noreply@microsoft.com' + publisherName: 'n/a' location: location tags: tags - applicationInsightsName: monitoring.outputs.applicationInsightsName + apis: [ + { + name: 'todo-api' + path: 'todo' + displayName: 'Simple Todo API' + apiDescription: 'This is a simple Todo API' + serviceUrl: 'https://${api.outputs.fqdn}' + subscriptionRequired: false + value: loadTextContent('../../../../../api/common/openapi.yaml') + policies: [ + { + value: replace(loadTextContent('../../../../../../common/infra/shared/gateway/apim/apim-api-policy.xml'), '{origin}', 'https://${web.outputs.fqdn}') + format: 'rawxml' + } + ] + } + ] } } // Configures the API in the Azure API Management (APIM) service -module apimApi '../../../../../common/infra/bicep/app/apim-api.bicep' = if (useAPIM) { - name: 'apim-api-deployment' +module apimsettings '../../../../../common/infra/bicep/app/apim-api-settings.bicep' = if (useAPIM) { + name: 'apim-api-settings' scope: rg params: { - name: useAPIM ? apim.outputs.apimServiceName : '' apiName: 'todo-api' - apiDisplayName: 'Simple Todo API' - apiDescription: 'This is a simple Todo API' + name: useAPIM ? apim.outputs.name : '' apiPath: 'todo' - webFrontendUrl: web.outputs.SERVICE_WEB_URI - apiBackendUrl: api.outputs.SERVICE_API_URI + applicationInsightsName: applicationInsights.outputs.name } } // Data outputs -output AZURE_COSMOS_CONNECTION_STRING_KEY string = cosmos.outputs.connectionStringKey -output AZURE_COSMOS_DATABASE_NAME string = cosmos.outputs.databaseName +output AZURE_COSMOS_CONNECTION_STRING_KEY string = connectionStringKey +output AZURE_COSMOS_DATABASE_NAME string = !empty(cosmosDatabaseName) ? cosmosDatabaseName: 'Todo' // 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 APPLICATIONINSIGHTS_CONNECTION_STRING string = applicationInsights.outputs.connectionString +output APPLICATIONINSIGHTS_NAME string = applicationInsights.outputs.name +output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerAppsEnvironment.outputs.name +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.outputs.loginServer +output AZURE_CONTAINER_REGISTRY_NAME string = containerRegistry.outputs.name +output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.uri output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name output AZURE_LOCATION string = location output AZURE_TENANT_ID string = tenant().tenantId -output API_BASE_URL string = useAPIM ? apimApi.outputs.SERVICE_API_URI : api.outputs.SERVICE_API_URI -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 API_BASE_URL string = useAPIM ? 'https://${apim.outputs.name}.azure-api.net/todo' : 'https://${api.outputs.fqdn}' +output REACT_APP_WEB_BASE_URL string = 'https://${web.outputs.fqdn}' +output SERVICE_API_NAME string = api.outputs.name +output SERVICE_WEB_NAME string = web.outputs.name output USE_APIM bool = useAPIM -output SERVICE_API_ENDPOINTS array = useAPIM ? [ apimApi.outputs.SERVICE_API_URI, api.outputs.SERVICE_API_URI ] : [] +output SERVICE_API_ENDPOINTS array = useAPIM ? [ 'https://${apim.outputs.name}.azure-api.net/todo', 'https://${api.outputs.fqdn}' ] : [] diff --git a/templates/todo/projects/python-mongo-aca/.repo/bicep/repo.yaml b/templates/todo/projects/python-mongo-aca/.repo/bicep/repo.yaml index 75a6a958b3c..731d9a18f59 100644 --- a/templates/todo/projects/python-mongo-aca/.repo/bicep/repo.yaml +++ b/templates/todo/projects/python-mongo-aca/.repo/bicep/repo.yaml @@ -87,6 +87,16 @@ repo: patterns: - "apim-api.bicep" + - from: ../../../api/common/openapi.yaml + to: ../src/api/openapi.yaml + patterns: + - "**/main.bicep" + + - from: ../../../common/infra/shared/gateway/apim/apim-api-policy.xml + to: ./app/apim-api-policy.xml + patterns: + - "**/main.bicep" + assets: # # Common assets @@ -111,6 +121,12 @@ repo: - from: ../../../../common/infra/bicep/app/cosmos-mongo-db.bicep to: ./infra/app/db.bicep + + - from: ../../../../common/infra/bicep/app/applicationinsights-dashboard.bicep + to: ./infra/app/applicationinsights-dashboard.bicep + + - from: ../../../../common/infra/bicep/app/apim-api-settings.bicep + to: ./infra/app/apim-api-settings.bicep - from: ./../../ to: ./