From 04895cc13891ea04655b3753f9dba41984bf6f2f Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 25 Oct 2022 14:06:09 +0800 Subject: [PATCH 01/39] Azdevify eshopOnWeb --- .gitignore | 2 + azure.yaml | 14 + infra/abbreviations.json | 135 ++ infra/app/dbCatalog.bicep | 25 + infra/app/dbIdentity.bicep | 25 + infra/app/web.bicep | 18 + infra/core/database/sqlserver1.bicep | 131 ++ infra/core/database/sqlserver2.bicep | 131 ++ .../core/host/appservice-config-cosmos.bicep | 18 + infra/core/host/appservice-config-logs.bicep | 11 + .../host/appservice-config-sqlserver.bicep | 14 + infra/core/host/appservice-config-union.bicep | 9 + infra/core/host/appservice-dotnet.bicep | 35 + infra/core/host/appservice-node.bicep | 35 + infra/core/host/appservice-python.bicep | 35 + infra/core/host/appservice.bicep | 100 ++ .../core/host/appserviceplan-functions.bicep | 21 + infra/core/host/appserviceplan-sites.bicep | 15 + infra/core/host/appserviceplan.bicep | 23 + infra/core/host/container-app.bicep | 79 ++ .../host/container-apps-environment.bicep | 30 + infra/core/host/container-apps.bicep | 30 + infra/core/host/container-registry.bicep | 40 + infra/core/host/functions-node.bicep | 34 + infra/core/host/functions-python.bicep | 34 + infra/core/host/functions.bicep | 58 + infra/core/host/staticwebapp.bicep | 25 + .../applicationinsights-dashboard.bicep | 1238 +++++++++++++++++ infra/core/monitor/applicationinsights.bicep | 30 + infra/core/monitor/loganalytics.bicep | 24 + infra/core/monitor/monitoring.bicep | 24 + infra/core/security/keyvault-access.bicep | 25 + infra/core/security/keyvault.bicep | 29 + infra/core/storage/storage-account.bicep | 29 + infra/main.bicep | 46 + infra/main.parameters.json | 21 + infra/resources.bicep | 82 ++ 37 files changed, 2675 insertions(+) create mode 100644 azure.yaml create mode 100644 infra/abbreviations.json create mode 100644 infra/app/dbCatalog.bicep create mode 100644 infra/app/dbIdentity.bicep create mode 100644 infra/app/web.bicep create mode 100644 infra/core/database/sqlserver1.bicep create mode 100644 infra/core/database/sqlserver2.bicep create mode 100644 infra/core/host/appservice-config-cosmos.bicep create mode 100644 infra/core/host/appservice-config-logs.bicep create mode 100644 infra/core/host/appservice-config-sqlserver.bicep create mode 100644 infra/core/host/appservice-config-union.bicep create mode 100644 infra/core/host/appservice-dotnet.bicep create mode 100644 infra/core/host/appservice-node.bicep create mode 100644 infra/core/host/appservice-python.bicep create mode 100644 infra/core/host/appservice.bicep create mode 100644 infra/core/host/appserviceplan-functions.bicep create mode 100644 infra/core/host/appserviceplan-sites.bicep create mode 100644 infra/core/host/appserviceplan.bicep create mode 100644 infra/core/host/container-app.bicep create mode 100644 infra/core/host/container-apps-environment.bicep create mode 100644 infra/core/host/container-apps.bicep create mode 100644 infra/core/host/container-registry.bicep create mode 100644 infra/core/host/functions-node.bicep create mode 100644 infra/core/host/functions-python.bicep create mode 100644 infra/core/host/functions.bicep create mode 100644 infra/core/host/staticwebapp.bicep create mode 100644 infra/core/monitor/applicationinsights-dashboard.bicep create mode 100644 infra/core/monitor/applicationinsights.bicep create mode 100644 infra/core/monitor/loganalytics.bicep create mode 100644 infra/core/monitor/monitoring.bicep create mode 100644 infra/core/security/keyvault-access.bicep create mode 100644 infra/core/security/keyvault.bicep create mode 100644 infra/core/storage/storage-account.bicep create mode 100644 infra/main.bicep create mode 100644 infra/main.parameters.json create mode 100644 infra/resources.bicep diff --git a/.gitignore b/.gitignore index 1148ecd..2407635 100644 --- a/.gitignore +++ b/.gitignore @@ -257,3 +257,5 @@ pub/ #Ignore marker-file used to know which docker files we have. .eshopdocker_* + +.azure diff --git a/azure.yaml b/azure.yaml new file mode 100644 index 0000000..da49708 --- /dev/null +++ b/azure.yaml @@ -0,0 +1,14 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json + +name: eShopOnWeb +services: + web: + project: ./src/Web + language: csharp + host: appservice +infra: + provider: "" + path: "" + module: "" +pipeline: + provider: "" diff --git a/infra/abbreviations.json b/infra/abbreviations.json new file mode 100644 index 0000000..a4fc9df --- /dev/null +++ b/infra/abbreviations.json @@ -0,0 +1,135 @@ +{ + "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", + "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/infra/app/dbCatalog.bicep b/infra/app/dbCatalog.bicep new file mode 100644 index 0000000..e320f63 --- /dev/null +++ b/infra/app/dbCatalog.bicep @@ -0,0 +1,25 @@ +param environmentName string +param location string = resourceGroup().location + +param databaseName string = 'CatalogDB' +param keyVaultName string + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +module sqlServer1 '../core/database/sqlserver1.bicep' = { + name: 'sqlServer1' + params: { + environmentName: environmentName + location: location + dbName: databaseName + keyVaultName: keyVaultName + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + } +} + +output sqlConnectionStringKey string = sqlServer1.outputs.sqlConnectionStringKey +output sqlDatabase1Name string = databaseName diff --git a/infra/app/dbIdentity.bicep b/infra/app/dbIdentity.bicep new file mode 100644 index 0000000..a9cb741 --- /dev/null +++ b/infra/app/dbIdentity.bicep @@ -0,0 +1,25 @@ +param environmentName string +param location string = resourceGroup().location + +param databaseName string = 'IdentityDB' +param keyVaultName string + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +module sqlServer2 '../core/database/sqlserver2.bicep' = { + name: 'sqlServer2' + params: { + environmentName: environmentName + location: location + dbName: databaseName + keyVaultName: keyVaultName + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + } +} + +output sqlConnectionStringKey string = sqlServer2.outputs.sqlConnectionStringKey +output sqlDatabase2Name string = databaseName diff --git a/infra/app/web.bicep b/infra/app/web.bicep new file mode 100644 index 0000000..828b282 --- /dev/null +++ b/infra/app/web.bicep @@ -0,0 +1,18 @@ +param environmentName string +param location string = resourceGroup().location +param appServicePlanId string + +param serviceName string = 'web' + +module web '../core/host/appservice-dotnet.bicep' = { + name: '${serviceName}-appservice-dotnet-module' + params: { + environmentName: environmentName + location: location + appServicePlanId: appServicePlanId + serviceName: serviceName + } +} + +output WEB_NAME string = web.outputs.name +output WEB_URI string = web.outputs.uri \ No newline at end of file diff --git a/infra/core/database/sqlserver1.bicep b/infra/core/database/sqlserver1.bicep new file mode 100644 index 0000000..671371c --- /dev/null +++ b/infra/core/database/sqlserver1.bicep @@ -0,0 +1,131 @@ +param environmentName string +param location string = resourceGroup().location + +param appUser string = 'appUser' +param dbName string +param keyVaultName string +param sqlAdmin string = 'sqlAdmin' +param sqlConnectionStringKey string = 'AZURE-SQL-CATALOG-CONNECTION-STRING' + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { + name: '${abbrs.sqlServers}${resourceToken}-Catalog' + location: location + tags: tags + properties: { + version: '12.0' + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + administratorLogin: sqlAdmin + administratorLoginPassword: sqlAdminPassword + } + + resource database 'databases' = { + name: dbName + 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: 'script-${resourceToken}-Catalog' + 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: dbName + } + { + 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 ${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: sqlConnectionStringKey + properties: { + value: '${azureSqlConnectionString}; Password=${appUserPassword}' + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +var azureSqlConnectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output sqlConnectionStringKey string = sqlConnectionStringKey diff --git a/infra/core/database/sqlserver2.bicep b/infra/core/database/sqlserver2.bicep new file mode 100644 index 0000000..430f48f --- /dev/null +++ b/infra/core/database/sqlserver2.bicep @@ -0,0 +1,131 @@ +param environmentName string +param location string = resourceGroup().location + +param appUser string = 'appUser' +param dbName string +param keyVaultName string +param sqlAdmin string = 'sqlAdmin' +param sqlConnectionStringKey string = 'AZURE-SQL-IDENTITY-CONNECTION-STRING' + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword string + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { + name: '${abbrs.sqlServers}${resourceToken}-Identity' + location: location + tags: tags + properties: { + version: '12.0' + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + administratorLogin: sqlAdmin + administratorLoginPassword: sqlAdminPassword + } + + resource database 'databases' = { + name: dbName + 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: 'script-${resourceToken}-Identity' + 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: dbName + } + { + 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 ${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: sqlConnectionStringKey + properties: { + value: '${azureSqlConnectionString}; Password=${appUserPassword}' + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: keyVaultName +} + +var azureSqlConnectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' +output sqlConnectionStringKey string = sqlConnectionStringKey diff --git a/infra/core/host/appservice-config-cosmos.bicep b/infra/core/host/appservice-config-cosmos.bicep new file mode 100644 index 0000000..65e91f9 --- /dev/null +++ b/infra/core/host/appservice-config-cosmos.bicep @@ -0,0 +1,18 @@ +param appServiceName string +param cosmosConnectionStringKey string = '' +param cosmosDatabaseName string = '' +param cosmosEndpoint string = '' + +module appServiceConfigCosmosSettings 'appservice-config-union.bicep' = { + name: '${appServiceName}-appservice-config-cosmos-settings' + params: { + appServiceName: appServiceName + configName: 'appsettings' + currentConfigProperties: list(resourceId('Microsoft.Web/sites/config', appServiceName, 'appsettings'), '2022-03-01').properties + additionalConfigProperties: { + AZURE_COSMOS_CONNECTION_STRING_KEY: cosmosConnectionStringKey + AZURE_COSMOS_DATABASE_NAME: cosmosDatabaseName + AZURE_COSMOS_ENDPOINT: cosmosEndpoint + } + } +} diff --git a/infra/core/host/appservice-config-logs.bicep b/infra/core/host/appservice-config-logs.bicep new file mode 100644 index 0000000..fde8940 --- /dev/null +++ b/infra/core/host/appservice-config-logs.bicep @@ -0,0 +1,11 @@ +param appServiceName string + +resource siteConfigLogs 'Microsoft.Web/sites/config@2022-03-01' = { + name: '${appServiceName}/logs' + properties: { + applicationLogs: { fileSystem: { level: 'Verbose' } } + detailedErrorMessages: { enabled: true } + failedRequestsTracing: { enabled: true } + httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } + } +} diff --git a/infra/core/host/appservice-config-sqlserver.bicep b/infra/core/host/appservice-config-sqlserver.bicep new file mode 100644 index 0000000..3d8c8d0 --- /dev/null +++ b/infra/core/host/appservice-config-sqlserver.bicep @@ -0,0 +1,14 @@ +param appServiceName string +param sqlConnectionStringKey string + +module appServiceConfigSqlServerSettings 'appservice-config-union.bicep' = { + name: '${appServiceName}-appservice-config-sqlserver-settings' + params: { + appServiceName: appServiceName + configName: 'appsettings' + currentConfigProperties: list(resourceId('Microsoft.Web/sites/config', appServiceName, 'appsettings'), '2022-03-01').properties + additionalConfigProperties: { + AZURE_SQL_CONNECTION_STRING_KEY: sqlConnectionStringKey + } + } +} diff --git a/infra/core/host/appservice-config-union.bicep b/infra/core/host/appservice-config-union.bicep new file mode 100644 index 0000000..3e0bcee --- /dev/null +++ b/infra/core/host/appservice-config-union.bicep @@ -0,0 +1,9 @@ +param additionalConfigProperties object +param appServiceName string +param configName string +param currentConfigProperties object + +resource siteConfigUnion 'Microsoft.Web/sites/config@2022-03-01' = { + name: '${appServiceName}/${configName}' + properties: union(currentConfigProperties, additionalConfigProperties) +} diff --git a/infra/core/host/appservice-dotnet.bicep b/infra/core/host/appservice-dotnet.bicep new file mode 100644 index 0000000..012a452 --- /dev/null +++ b/infra/core/host/appservice-dotnet.bicep @@ -0,0 +1,35 @@ +param environmentName string +param location string = resourceGroup().location + +param allowedOrigins array = [] +param appCommandLine string = '' +param applicationInsightsName string = '' +param appServicePlanId string +param appSettings object = {} +param keyVaultName string = '' +param linuxFxVersion string = 'DOTNETCORE|6.0' +param managedIdentity bool = !(empty(keyVaultName)) +param scmDoBuildDuringDeployment bool = false +param serviceName string + +module appService 'appservice.bicep' = { + name: '${serviceName}-appservice-dotnet' + params: { + environmentName: environmentName + location: location + allowedOrigins: allowedOrigins + appCommandLine: appCommandLine + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: appSettings + keyVaultName: keyVaultName + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + serviceName: serviceName + } +} + +output identityPrincipalId string = appService.outputs.identityPrincipalId +output name string = appService.outputs.name +output uri string = appService.outputs.uri diff --git a/infra/core/host/appservice-node.bicep b/infra/core/host/appservice-node.bicep new file mode 100644 index 0000000..6f56d46 --- /dev/null +++ b/infra/core/host/appservice-node.bicep @@ -0,0 +1,35 @@ +param environmentName string +param location string = resourceGroup().location + +param allowedOrigins array = [] +param appCommandLine string = '' +param applicationInsightsName string = '' +param appServicePlanId string +param appSettings object = {} +param keyVaultName string = '' +param linuxFxVersion string = 'NODE|16-lts' +param managedIdentity bool = !(empty(keyVaultName)) +param scmDoBuildDuringDeployment bool = false +param serviceName string + +module appService 'appservice.bicep' = { + name: '${serviceName}-appservice-node' + params: { + environmentName: environmentName + location: location + allowedOrigins: allowedOrigins + appCommandLine: appCommandLine + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: appSettings + keyVaultName: keyVaultName + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + serviceName: serviceName + } +} + +output identityPrincipalId string = appService.outputs.identityPrincipalId +output name string = appService.outputs.name +output uri string = appService.outputs.uri diff --git a/infra/core/host/appservice-python.bicep b/infra/core/host/appservice-python.bicep new file mode 100644 index 0000000..767c127 --- /dev/null +++ b/infra/core/host/appservice-python.bicep @@ -0,0 +1,35 @@ +param environmentName string +param location string = resourceGroup().location + +param allowedOrigins array = [] +param appCommandLine string = '' +param applicationInsightsName string = '' +param appServicePlanId string +param appSettings object = {} +param keyVaultName string = '' +param linuxFxVersion string = 'PYTHON|3.8' +param managedIdentity bool = !(empty(keyVaultName)) +param scmDoBuildDuringDeployment bool = true +param serviceName string + +module appService 'appservice.bicep' = { + name: '${serviceName}-appservice-python' + params: { + environmentName: environmentName + location: location + allowedOrigins: allowedOrigins + appCommandLine: appCommandLine + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: appSettings + keyVaultName: keyVaultName + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + serviceName: serviceName + } +} + +output identityPrincipalId string = appService.outputs.identityPrincipalId +output name string = appService.outputs.name +output uri string = appService.outputs.uri diff --git a/infra/core/host/appservice.bicep b/infra/core/host/appservice.bicep new file mode 100644 index 0000000..3eece63 --- /dev/null +++ b/infra/core/host/appservice.bicep @@ -0,0 +1,100 @@ +param environmentName string +param location string = resourceGroup().location + +param allowedOrigins array = [] +param alwaysOn bool = true +param appCommandLine string = '' +param applicationInsightsName string = '' +param appServicePlanId string +param appSettings object = {} +param clientAffinityEnabled bool = false +param functionAppScaleLimit int = -1 +param keyVaultName string = '' +param kind string = 'app,linux' +param linuxFxVersion string = '' +param managedIdentity bool = !(empty(keyVaultName)) +param minimumElasticInstanceCount int = -1 +param numberOfWorkers int = -1 +param scmDoBuildDuringDeployment bool = false +param serviceName string +param use32BitWorkerProcess bool = false + +var abbrs = loadJsonContent('../../abbreviations.json') +var tags = { 'azd-env-name': environmentName } +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) + +var prefix = contains(kind, 'function') ? abbrs.webSitesFunctions : abbrs.webSitesAppService + +resource appService 'Microsoft.Web/sites@2022-03-01' = { + name: '${prefix}${serviceName}-${resourceToken}' + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + kind: kind + properties: { + serverFarmId: appServicePlanId + siteConfig: { + linuxFxVersion: linuxFxVersion + alwaysOn: alwaysOn + ftpsState: 'FtpsOnly' + appCommandLine: appCommandLine + numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null + minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null + use32BitWorkerProcess: use32BitWorkerProcess + functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null + cors: { + allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) + } + } + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: true + } + + identity: managedIdentity ? { type: 'SystemAssigned' } : null + + resource appSettings 'config' = { + name: 'appsettings' + properties: union({ + SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) + }, + !(empty(applicationInsightsName)) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, + !(empty(keyVaultName)) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) + } +} + +module appSettingsUnion 'appservice-config-union.bicep' = if (!empty(appSettings)) { + name: '${serviceName}-app-settings-union' + params: { + appServiceName: appService.name + configName: 'appsettings' + currentConfigProperties: appService::appSettings.list().properties + additionalConfigProperties: appSettings + } +} + +module siteConfigLogs 'appservice-config-logs.bicep' = { + name: '${serviceName}-appservice-config-logs' + params: { + appServiceName: appService.name + } +} + +module keyVaultAccess '../security/keyvault-access.bicep' = if (!(empty(keyVaultName))) { + name: '${serviceName}-appservice-keyvault-access' + params: { + principalId: appService.identity.principalId + environmentName: environmentName + location: location + } +} + +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/infra/core/host/appserviceplan-functions.bicep b/infra/core/host/appserviceplan-functions.bicep new file mode 100644 index 0000000..6693e6b --- /dev/null +++ b/infra/core/host/appserviceplan-functions.bicep @@ -0,0 +1,21 @@ +param environmentName string +param location string = resourceGroup().location + +param sku object = { + name: 'Y1' + tier: 'Dynamic' + size: 'Y1' + family: 'Y' +} + +module appServicePlanFunctions 'appserviceplan.bicep' = { + name: 'appserviceplan-functions' + params: { + environmentName: environmentName + location: location + sku: sku + kind: 'functionapp' + } +} + +output appServicePlanId string = appServicePlanFunctions.outputs.appServicePlanId diff --git a/infra/core/host/appserviceplan-sites.bicep b/infra/core/host/appserviceplan-sites.bicep new file mode 100644 index 0000000..bd35137 --- /dev/null +++ b/infra/core/host/appserviceplan-sites.bicep @@ -0,0 +1,15 @@ +param environmentName string +param location string = resourceGroup().location + +param sku object = { name: 'B1' } + +module appServicePlanSites 'appserviceplan.bicep' = { + name: 'appserviceplan-sites' + params: { + environmentName: environmentName + location: location + sku: sku + } +} + +output appServicePlanId string = appServicePlanSites.outputs.appServicePlanId diff --git a/infra/core/host/appserviceplan.bicep b/infra/core/host/appserviceplan.bicep new file mode 100644 index 0000000..c72fcbd --- /dev/null +++ b/infra/core/host/appserviceplan.bicep @@ -0,0 +1,23 @@ +param environmentName string +param location string = resourceGroup().location + +param kind string = '' +param reserved bool = true +param sku object + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: '${abbrs.webServerFarms}${resourceToken}' + location: location + tags: tags + sku: sku + kind: kind + properties: { + reserved: reserved + } +} + +output appServicePlanId string = appServicePlan.id diff --git a/infra/core/host/container-app.bicep b/infra/core/host/container-app.bicep new file mode 100644 index 0000000..fdfcf67 --- /dev/null +++ b/infra/core/host/container-app.bicep @@ -0,0 +1,79 @@ +param environmentName string +param location string = resourceGroup().location + +param containerAppsEnvironmentName string = '' +param containerRegistryName string = '' +param env array = [] +param external bool = true +param imageName string +param keyVaultName string = '' +param managedIdentity bool = !(empty(keyVaultName)) +param targetPort int = 80 +param serviceName string + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource app 'Microsoft.App/containerApps@2022-03-01' = { + name: '${abbrs.appContainerApps}${serviceName}-${resourceToken}' + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identity: managedIdentity ? { type: 'SystemAssigned' } : null + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + activeRevisionsMode: 'single' + ingress: { + external: external + targetPort: targetPort + transport: 'auto' + } + secrets: [ + { + name: 'registry-password' + value: containerRegistry.listCredentials().passwords[0].value + } + ] + registries: [ + { + server: '${containerRegistry.name}.azurecr.io' + username: containerRegistry.name + passwordSecretRef: 'registry-password' + } + ] + } + template: { + containers: [ + { + image: imageName + name: 'main' + env: env + } + ] + } + } +} + +module keyVaultAccess '../security/keyvault-access.bicep' = if (!(empty(keyVaultName))) { + name: '${serviceName}-appservice-keyvault-access' + params: { + environmentName: environmentName + location: location + keyVaultName: keyVaultName + principalId: app.identity.principalId + } +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = { + name: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' +} + +// 2022-02-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { + name: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' +} + +output identityPrincipalId string = managedIdentity ? app.identity.principalId : '' +output name string = app.name +output uri string = 'https://${app.properties.configuration.ingress.fqdn}' diff --git a/infra/core/host/container-apps-environment.bicep b/infra/core/host/container-apps-environment.bicep new file mode 100644 index 0000000..b1e0578 --- /dev/null +++ b/infra/core/host/container-apps-environment.bicep @@ -0,0 +1,30 @@ +param environmentName string +param location string = resourceGroup().location + +param containerAppsEnvironmentName string = '' +param logAnalyticsWorkspaceName string + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' = { + name: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' existing = { + name: logAnalyticsWorkspaceName +} + +output containerAppsEnvironmentName string = containerAppsEnvironment.name diff --git a/infra/core/host/container-apps.bicep b/infra/core/host/container-apps.bicep new file mode 100644 index 0000000..a916c7f --- /dev/null +++ b/infra/core/host/container-apps.bicep @@ -0,0 +1,30 @@ +param environmentName string +param location string = resourceGroup().location + +param containerAppsEnvironmentName string = '' +param containerAppsGroupName string = 'app' +param containerRegistryName string = '' +param logAnalyticsWorkspaceName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${containerAppsGroupName}-container-apps-environment' + params: { + environmentName: environmentName + location: location + containerAppsEnvironmentName: containerAppsEnvironmentName + logAnalyticsWorkspaceName: logAnalyticsWorkspaceName + } +} + +module containerRegistry 'container-registry.bicep' = { + name: '${containerAppsGroupName}-container-registry' + params: { + environmentName: environmentName + location: location + containerRegistryName: containerRegistryName + } +} + +output containerAppsEnvironmentName string = containerAppsEnvironment.outputs.containerAppsEnvironmentName +output containerRegistryEndpoint string = containerRegistry.outputs.containerRegistryEndpoint +output containerRegistryName string = containerRegistry.outputs.containerRegistryName diff --git a/infra/core/host/container-registry.bicep b/infra/core/host/container-registry.bicep new file mode 100644 index 0000000..115da50 --- /dev/null +++ b/infra/core/host/container-registry.bicep @@ -0,0 +1,40 @@ +param environmentName string +param location string = resourceGroup().location + +param adminUserEnabled bool = true +param anonymousPullEnabled bool = false +param containerRegistryName string = '' +param dataEndpointEnabled bool = false +param encryption object = { + status: 'disabled' +} +param networkRuleBypassOptions string = 'AzureServices' +param publicNetworkAccess string = 'Enabled' +param sku object = { + name: 'Standard' +} +param zoneRedundancy string = 'Disabled' + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +// 2022-02-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { + name: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + location: location + tags: tags + sku: sku + properties: { + adminUserEnabled: adminUserEnabled + anonymousPullEnabled: anonymousPullEnabled + dataEndpointEnabled: dataEndpointEnabled + encryption: encryption + networkRuleBypassOptions: networkRuleBypassOptions + publicNetworkAccess: publicNetworkAccess + zoneRedundancy: zoneRedundancy + } +} + +output containerRegistryEndpoint string = containerRegistry.properties.loginServer +output containerRegistryName string = containerRegistry.name diff --git a/infra/core/host/functions-node.bicep b/infra/core/host/functions-node.bicep new file mode 100644 index 0000000..e4731f4 --- /dev/null +++ b/infra/core/host/functions-node.bicep @@ -0,0 +1,34 @@ +param environmentName string +param location string = resourceGroup().location + +param allowedOrigins array = [] +param applicationInsightsName string = '' +param appServicePlanId string +param appSettings object = {} +param keyVaultName string = '' +param linuxFxVersion string = 'NODE|16' +param managedIdentity bool = !(empty(keyVaultName)) +param serviceName string +param storageAccountName string + +module functions 'functions.bicep' = { + name: '${serviceName}-functions-node' + params: { + environmentName: environmentName + location: location + allowedOrigins: allowedOrigins + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: appSettings + functionsWorkerRuntime: 'node' + keyVaultName: keyVaultName + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + serviceName: serviceName + storageAccountName: storageAccountName + } +} + +output identityPrincipalId string = functions.outputs.identityPrincipalId +output name string = functions.outputs.name +output uri string = functions.outputs.uri diff --git a/infra/core/host/functions-python.bicep b/infra/core/host/functions-python.bicep new file mode 100644 index 0000000..2ff79bd --- /dev/null +++ b/infra/core/host/functions-python.bicep @@ -0,0 +1,34 @@ +param environmentName string +param location string = resourceGroup().location + +param allowedOrigins array = [] +param applicationInsightsName string = '' +param appServicePlanId string +param appSettings object = {} +param keyVaultName string = '' +param linuxFxVersion string = 'PYTHON|3.8' +param managedIdentity bool = !(empty(keyVaultName)) +param serviceName string +param storageAccountName string + +module functions 'functions.bicep' = { + name: '${serviceName}-functions-python' + params: { + environmentName: environmentName + location: location + allowedOrigins: allowedOrigins + applicationInsightsName: applicationInsightsName + appServicePlanId: appServicePlanId + appSettings: appSettings + functionsWorkerRuntime: 'python' + keyVaultName: keyVaultName + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + serviceName: serviceName + storageAccountName: storageAccountName + } +} + +output identityPrincipalId string = functions.outputs.identityPrincipalId +output name string = functions.outputs.name +output uri string = functions.outputs.uri diff --git a/infra/core/host/functions.bicep b/infra/core/host/functions.bicep new file mode 100644 index 0000000..818485f --- /dev/null +++ b/infra/core/host/functions.bicep @@ -0,0 +1,58 @@ +param environmentName string +param location string = resourceGroup().location + +param allowedOrigins array = [] +param alwaysOn bool = false +param applicationInsightsName string = '' +param appServicePlanId string +param appSettings object = {} +param clientAffinityEnabled bool = false +param functionAppScaleLimit int = 200 +param functionsExtensionVersion string = '~4' +param functionsWorkerRuntime string +param kind string = 'functionapp,linux' +param linuxFxVersion string = '' +param keyVaultName string = '' +param managedIdentity bool = !(empty(keyVaultName)) +param minimumElasticInstanceCount int = 0 +param numberOfWorkers int = 1 +param scmDoBuildDuringDeployment bool = true +param serviceName string +param storageAccountName string +param use32BitWorkerProcess bool = false + +module functions 'appservice.bicep' = { + name: '${serviceName}-functions' + params: { + environmentName: environmentName + location: location + allowedOrigins: allowedOrigins + alwaysOn: alwaysOn + 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: functionsExtensionVersion + FUNCTIONS_WORKER_RUNTIME: functionsWorkerRuntime + }) + clientAffinityEnabled: clientAffinityEnabled + functionAppScaleLimit: functionAppScaleLimit + keyVaultName: keyVaultName + kind: kind + linuxFxVersion: linuxFxVersion + managedIdentity: managedIdentity + minimumElasticInstanceCount: minimumElasticInstanceCount + numberOfWorkers: numberOfWorkers + scmDoBuildDuringDeployment: scmDoBuildDuringDeployment + serviceName: serviceName + 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/infra/core/host/staticwebapp.bicep b/infra/core/host/staticwebapp.bicep new file mode 100644 index 0000000..3537fa9 --- /dev/null +++ b/infra/core/host/staticwebapp.bicep @@ -0,0 +1,25 @@ +param environmentName string +param location string = resourceGroup().location + +param serviceName string +param sku object = { + name: 'Free' + tier: 'Free' +} + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource web 'Microsoft.Web/staticSites@2022-03-01' = { + name: '${abbrs.webStaticSites}${serviceName}-${resourceToken}' + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + sku: sku + properties: { + provider: 'Custom' + } +} + +output name string = web.name +output uri string = 'https://${web.properties.defaultHostname}' diff --git a/infra/core/monitor/applicationinsights-dashboard.bicep b/infra/core/monitor/applicationinsights-dashboard.bicep new file mode 100644 index 0000000..11a6512 --- /dev/null +++ b/infra/core/monitor/applicationinsights-dashboard.bicep @@ -0,0 +1,1238 @@ +param environmentName string +param location string = resourceGroup().location +param applicationInsightsName string + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +// 2020-09-01-preview because that is the latest valid version +resource applicationInsightsDashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = { + name: '${abbrs.portalDashboards}${resourceToken}' + 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/infra/core/monitor/applicationinsights.bicep b/infra/core/monitor/applicationinsights.bicep new file mode 100644 index 0000000..b9ce1fd --- /dev/null +++ b/infra/core/monitor/applicationinsights.bicep @@ -0,0 +1,30 @@ +param environmentName string +param location string = resourceGroup().location +param logAnalyticsWorkspaceId string + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: '${abbrs.insightsComponents}${resourceToken}' + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = { + name: 'application-insights-dashboard' + params: { + environmentName: environmentName + location: location + applicationInsightsName: applicationInsights.name + } +} + +output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString +output applicationInsightsName string = applicationInsights.name diff --git a/infra/core/monitor/loganalytics.bicep b/infra/core/monitor/loganalytics.bicep new file mode 100644 index 0000000..a36912c --- /dev/null +++ b/infra/core/monitor/loganalytics.bicep @@ -0,0 +1,24 @@ +param environmentName string +param location string = resourceGroup().location + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + location: location + tags: tags + properties: any({ + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + }) +} + +output logAnalyticsWorkspaceId string = logAnalytics.id +output logAnalyticsWorkspaceName string = logAnalytics.name diff --git a/infra/core/monitor/monitoring.bicep b/infra/core/monitor/monitoring.bicep new file mode 100644 index 0000000..1c5ae3e --- /dev/null +++ b/infra/core/monitor/monitoring.bicep @@ -0,0 +1,24 @@ +param environmentName string +param location string = resourceGroup().location + +module logAnalytics 'loganalytics.bicep' = { + name: 'loganalytics' + params: { + environmentName: environmentName + location: location + } +} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + environmentName: environmentName + location: location + logAnalyticsWorkspaceId: logAnalytics.outputs.logAnalyticsWorkspaceId + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.applicationInsightsConnectionString +output applicationInsightsName string = applicationInsights.outputs.applicationInsightsName +output logAnalyticsWorkspaceId string = logAnalytics.outputs.logAnalyticsWorkspaceId +output logAnalyticsWorkspaceName string = logAnalytics.outputs.logAnalyticsWorkspaceName diff --git a/infra/core/security/keyvault-access.bicep b/infra/core/security/keyvault-access.bicep new file mode 100644 index 0000000..30c00f4 --- /dev/null +++ b/infra/core/security/keyvault-access.bicep @@ -0,0 +1,25 @@ +param environmentName string +param location string = resourceGroup().location + +param keyVaultName string = '' +param permissions object = { secrets: [ 'get', 'list' ] } +param principalId string + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) + +resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = { + parent: keyVault + name: 'add' + properties: { + accessPolicies: [ { + objectId: principalId + tenantId: subscription().tenantId + permissions: permissions + } ] + } +} + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { + name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' +} diff --git a/infra/core/security/keyvault.bicep b/infra/core/security/keyvault.bicep new file mode 100644 index 0000000..96b8e84 --- /dev/null +++ b/infra/core/security/keyvault.bicep @@ -0,0 +1,29 @@ +param environmentName string +param location string = resourceGroup().location + +param keyVaultName string = '' +param principalId string = '' + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' + 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 keyVaultEndpoint string = keyVault.properties.vaultUri +output keyVaultName string = keyVault.name diff --git a/infra/core/storage/storage-account.bicep b/infra/core/storage/storage-account.bicep new file mode 100644 index 0000000..7d2eb91 --- /dev/null +++ b/infra/core/storage/storage-account.bicep @@ -0,0 +1,29 @@ +param environmentName string +param location string = resourceGroup().location + +param allowBlobPublicAccess bool = false +param kind string = 'StorageV2' +param minimumTlsVersion string = 'TLS1_2' +param sku object = { name: 'Standard_LRS' } + +var abbrs = loadJsonContent('../../abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) +var tags = { 'azd-env-name': environmentName } + +resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { + name: '${abbrs.storageStorageAccounts}${resourceToken}' + location: location + tags: tags + kind: kind + sku: sku + properties: { + minimumTlsVersion: minimumTlsVersion + allowBlobPublicAccess: allowBlobPublicAccess + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Allow' + } + } +} + +output name string = storage.name diff --git a/infra/main.bicep b/infra/main.bicep new file mode 100644 index 0000000..a47a36f --- /dev/null +++ b/infra/main.bicep @@ -0,0 +1,46 @@ +targetScope = 'subscription' + +@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('Id of the user or app to assign application roles') +param principalId string = '' + +@secure() +@description('SQL Server administrator password') +param sqlAdminPassword string + +@secure() +@description('Application user password') +param appUserPassword string + +var abbrs = loadJsonContent('./abbreviations.json') +var tags = { 'azd-env-name': environmentName } + +resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: '${abbrs.resourcesResourceGroups}${environmentName}' + location: location + tags: tags +} + +module resources 'resources.bicep' = { + name: 'resources' + scope: rg + params: { + environmentName: environmentName + location: location + principalId: principalId + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + } +} + +output AZURE_LOCATION string = location +output AZURE_TENANT_ID string = tenant().tenantId +output REACT_APP_WEB_BASE_URL string = resources.outputs.WEB_URI diff --git a/infra/main.parameters.json b/infra/main.parameters.json new file mode 100644 index 0000000..0ef1d97 --- /dev/null +++ b/infra/main.parameters.json @@ -0,0 +1,21 @@ +{ + "$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}" + }, + "sqlAdminPassword": { + "value": "$(secretOrRandomPassword ${AZURE_KEY_VAULT_NAME} sqlAdminPassword)" + }, + "appUserPassword": { + "value": "$(secretOrRandomPassword ${AZURE_KEY_VAULT_NAME} appUserPassword)" + } + } +} \ No newline at end of file diff --git a/infra/resources.bicep b/infra/resources.bicep new file mode 100644 index 0000000..0eafb44 --- /dev/null +++ b/infra/resources.bicep @@ -0,0 +1,82 @@ +param environmentName string +param location string = resourceGroup().location +param principalId string = '' + +@secure() +param sqlAdminPassword string + +@secure() +param appUserPassword string + +// The application frontend +module web './app/web.bicep' = { + name: 'web' + params: { + environmentName: environmentName + location: location + appServicePlanId: appServicePlan.outputs.appServicePlanId + } +} + +// The application database: Catalog +module sqlServer1 './app/dbCatalog.bicep' = { + name: 'sqlCatalog' + params: { + environmentName: environmentName + location: location + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + keyVaultName: keyVault.outputs.keyVaultName + } +} + +// The application database: Identity +module sqlServer2 './app/dbIdentity.bicep' = { + name: 'sqlIdentity' + params: { + environmentName: environmentName + location: location + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + keyVaultName: keyVault.outputs.keyVaultName + } +} + +// Configure web to use sqlCatalog +module apiSqlServerConfig1 './core/host/appservice-config-sqlserver.bicep' = { + name: 'web-sqlserver-config-1' + params: { + appServiceName: web.outputs.WEB_NAME + sqlConnectionStringKey: sqlServer1.outputs.sqlConnectionStringKey + } +} + +// Configure web to use sqlIdentity +module apiSqlServerConfig2 './core/host/appservice-config-sqlserver.bicep' = { + name: 'web-sqlserver-config-2' + params: { + appServiceName: web.outputs.WEB_NAME + sqlConnectionStringKey: sqlServer2.outputs.sqlConnectionStringKey + } +} + +// Store secrets in a keyvault +module keyVault './core/security/keyvault.bicep' = { + name: 'keyvault' + params: { + environmentName: environmentName + location: location + principalId: principalId + } +} + +// Create an App Service Plan to group applications under the same payment plan and SKU +module appServicePlan './core/host/appserviceplan-sites.bicep' = { + name: 'appserviceplan' + params: { + environmentName: environmentName + location: location + } +} + +output WEB_URI string = web.outputs.WEB_URI From c7831749337784e49ddff559a1987a12bfee4d2c Mon Sep 17 00:00:00 2001 From: zedy Date: Fri, 18 Nov 2022 10:23:31 +0800 Subject: [PATCH 02/39] Azdev-ify changes for latest /bicep/core --- src/Web/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Web/appsettings.json b/src/Web/appsettings.json index 70989a6..4f5c0ed 100644 --- a/src/Web/appsettings.json +++ b/src/Web/appsettings.json @@ -4,8 +4,8 @@ "webBase": "https://localhost:44315/" }, "ConnectionStrings": { - "CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;", - "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" + "CatalogConnection": "Server=sqlserver-catalog-01.database.windows.net; Database=Todo; User=appUser; Password=fACZr419yzJ19tA", + "IdentityConnection": "Server=sqlserver-identity-01.database.windows.net; Database=Todo; User=appUser; Password=fACZr419yzJ19tA" }, "CatalogBaseUrl": "", "Logging": { From 88a27783a1e0615fcee74d0852c5aa0b21c1a07d Mon Sep 17 00:00:00 2001 From: zedy Date: Fri, 18 Nov 2022 10:25:09 +0800 Subject: [PATCH 03/39] Azdev-ify changes for latest /bicep/core --- .gitignore | 1 + azure.yaml | 6 - infra/app/catalog-db.bicep | 31 +++++ infra/app/dbCatalog.bicep | 25 ---- infra/app/dbIdentity.bicep | 25 ---- infra/app/identity-db.bicep | 31 +++++ infra/app/web.bicep | 23 ++-- .../core/database/cosmos/cosmos-account.bicep | 48 +++++++ .../cosmos/mongo/cosmos-mongo-account.bicep | 22 +++ .../cosmos/mongo/cosmos-mongo-db.bicep | 46 +++++++ .../cosmos/sql/cosmos-sql-account.bicep | 21 +++ .../database/cosmos/sql/cosmos-sql-db.bicep | 73 ++++++++++ .../cosmos/sql/cosmos-sql-role-assign.bicep | 18 +++ .../cosmos/sql/cosmos-sql-role-def.bicep | 29 ++++ infra/core/database/sqlserver/sqlserver.bicep | 129 ++++++++++++++++++ .../database/{ => sqlserver}/sqlserver1.bicep | 30 ++-- .../database/{ => sqlserver}/sqlserver2.bicep | 30 ++-- .../core/host/appservice-config-cosmos.bicep | 18 --- infra/core/host/appservice-config-logs.bicep | 11 -- .../host/appservice-config-sqlserver.bicep | 14 -- infra/core/host/appservice-config-union.bicep | 9 -- infra/core/host/appservice-dotnet.bicep | 35 ----- infra/core/host/appservice-node.bicep | 35 ----- infra/core/host/appservice-python.bicep | 35 ----- infra/core/host/appservice.bicep | 89 ++++++------ .../core/host/appserviceplan-functions.bicep | 21 --- infra/core/host/appserviceplan-sites.bicep | 15 -- infra/core/host/appserviceplan.bicep | 11 +- infra/core/host/container-app.bicep | 42 +++--- .../host/container-apps-environment.bicep | 14 +- infra/core/host/container-apps.bicep | 22 +-- infra/core/host/container-registry.bicep | 16 +-- infra/core/host/functions-node.bicep | 34 ----- infra/core/host/functions-python.bicep | 34 ----- infra/core/host/functions.bicep | 62 ++++++--- infra/core/host/staticwebapp.bicep | 12 +- .../applicationinsights-dashboard.bicep | 11 +- infra/core/monitor/applicationinsights.bicep | 18 +-- infra/core/monitor/loganalytics.bicep | 13 +- infra/core/monitor/monitoring.bicep | 23 ++-- infra/core/security/keyvault-access.bicep | 10 +- infra/core/security/keyvault.bicep | 14 +- infra/core/storage/storage-account.bicep | 23 +++- infra/main.bicep | 84 +++++++++++- infra/resources.bicep | 82 ----------- 45 files changed, 762 insertions(+), 633 deletions(-) create mode 100644 infra/app/catalog-db.bicep delete mode 100644 infra/app/dbCatalog.bicep delete mode 100644 infra/app/dbIdentity.bicep create mode 100644 infra/app/identity-db.bicep create mode 100644 infra/core/database/cosmos/cosmos-account.bicep create mode 100644 infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep create mode 100644 infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep create mode 100644 infra/core/database/cosmos/sql/cosmos-sql-account.bicep create mode 100644 infra/core/database/cosmos/sql/cosmos-sql-db.bicep create mode 100644 infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep create mode 100644 infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep create mode 100644 infra/core/database/sqlserver/sqlserver.bicep rename infra/core/database/{ => sqlserver}/sqlserver1.bicep (76%) rename infra/core/database/{ => sqlserver}/sqlserver2.bicep (76%) delete mode 100644 infra/core/host/appservice-config-cosmos.bicep delete mode 100644 infra/core/host/appservice-config-logs.bicep delete mode 100644 infra/core/host/appservice-config-sqlserver.bicep delete mode 100644 infra/core/host/appservice-config-union.bicep delete mode 100644 infra/core/host/appservice-dotnet.bicep delete mode 100644 infra/core/host/appservice-node.bicep delete mode 100644 infra/core/host/appservice-python.bicep delete mode 100644 infra/core/host/appserviceplan-functions.bicep delete mode 100644 infra/core/host/appserviceplan-sites.bicep delete mode 100644 infra/core/host/functions-node.bicep delete mode 100644 infra/core/host/functions-python.bicep delete mode 100644 infra/resources.bicep diff --git a/.gitignore b/.gitignore index 2407635..91a682b 100644 --- a/.gitignore +++ b/.gitignore @@ -257,5 +257,6 @@ pub/ #Ignore marker-file used to know which docker files we have. .eshopdocker_* +.devcontainer .azure diff --git a/azure.yaml b/azure.yaml index da49708..9590449 100644 --- a/azure.yaml +++ b/azure.yaml @@ -6,9 +6,3 @@ services: project: ./src/Web language: csharp host: appservice -infra: - provider: "" - path: "" - module: "" -pipeline: - provider: "" diff --git a/infra/app/catalog-db.bicep b/infra/app/catalog-db.bicep new file mode 100644 index 0000000..631f138 --- /dev/null +++ b/infra/app/catalog-db.bicep @@ -0,0 +1,31 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param databaseName string = 'CatalogDB' +param keyVaultName string + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword 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 sqlServer1 '../core/database/sqlserver/sqlserver1.bicep' = { + name: 'sqlServer01' + params: { + name: name + location: location + tags: tags + databaseName: actualDatabaseName + keyVaultName: keyVaultName + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + } +} + +output sqlCatalogConnectionStringKey string = sqlServer1.outputs.connectionStringKey +output sqlCatalogDatabase1Name string = sqlServer1.outputs.databaseName diff --git a/infra/app/dbCatalog.bicep b/infra/app/dbCatalog.bicep deleted file mode 100644 index e320f63..0000000 --- a/infra/app/dbCatalog.bicep +++ /dev/null @@ -1,25 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param databaseName string = 'CatalogDB' -param keyVaultName string - -@secure() -param sqlAdminPassword string -@secure() -param appUserPassword string - -module sqlServer1 '../core/database/sqlserver1.bicep' = { - name: 'sqlServer1' - params: { - environmentName: environmentName - location: location - dbName: databaseName - keyVaultName: keyVaultName - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - } -} - -output sqlConnectionStringKey string = sqlServer1.outputs.sqlConnectionStringKey -output sqlDatabase1Name string = databaseName diff --git a/infra/app/dbIdentity.bicep b/infra/app/dbIdentity.bicep deleted file mode 100644 index a9cb741..0000000 --- a/infra/app/dbIdentity.bicep +++ /dev/null @@ -1,25 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param databaseName string = 'IdentityDB' -param keyVaultName string - -@secure() -param sqlAdminPassword string -@secure() -param appUserPassword string - -module sqlServer2 '../core/database/sqlserver2.bicep' = { - name: 'sqlServer2' - params: { - environmentName: environmentName - location: location - dbName: databaseName - keyVaultName: keyVaultName - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - } -} - -output sqlConnectionStringKey string = sqlServer2.outputs.sqlConnectionStringKey -output sqlDatabase2Name string = databaseName diff --git a/infra/app/identity-db.bicep b/infra/app/identity-db.bicep new file mode 100644 index 0000000..1673c53 --- /dev/null +++ b/infra/app/identity-db.bicep @@ -0,0 +1,31 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param databaseName string = 'IdentityDB' +param keyVaultName string + +@secure() +param sqlAdminPassword string +@secure() +param appUserPassword 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 sqlServer2 '../core/database/sqlserver/sqlserver2.bicep' = { + name: 'sqlServer02' + params: { + name: name + location: location + tags: tags + databaseName: actualDatabaseName + keyVaultName: keyVaultName + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + } +} + +output sqlCatalogConnectionStringKey string = sqlServer2.outputs.connectionStringKey +output sqlCatalogDatabase1Name string = sqlServer2.outputs.databaseName diff --git a/infra/app/web.bicep b/infra/app/web.bicep index 828b282..f33a276 100644 --- a/infra/app/web.bicep +++ b/infra/app/web.bicep @@ -1,18 +1,23 @@ -param environmentName string +param name string param location string = resourceGroup().location -param appServicePlanId string - +param tags object = {} param serviceName string = 'web' +param appCommandLine string = 'pm2 serve /home/site/wwwroot --no-daemon --spa' +param applicationInsightsName string = '' +param appServicePlanId string +param appSettings object = {} -module web '../core/host/appservice-dotnet.bicep' = { - name: '${serviceName}-appservice-dotnet-module' +module web '../core/host/appservice.bicep' = { + name: '${name}-deployment' params: { - environmentName: environmentName + name: name location: location appServicePlanId: appServicePlanId - serviceName: serviceName + runtimeName: 'dotnetcore' + runtimeVersion: '6.0' + tags: union(tags, { 'azd-service-name': serviceName }) + scmDoBuildDuringDeployment: false } } -output WEB_NAME string = web.outputs.name -output WEB_URI string = web.outputs.uri \ No newline at end of file +output REACT_APP_WEB_BASE_URL string = web.outputs.uri diff --git a/infra/core/database/cosmos/cosmos-account.bicep b/infra/core/database/cosmos/cosmos-account.bicep new file mode 100644 index 0000000..6bc1f2e --- /dev/null +++ b/infra/core/database/cosmos/cosmos-account.bicep @@ -0,0 +1,48 @@ +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.0' } : {} + 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/infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep b/infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep new file mode 100644 index 0000000..bd2a2b5 --- /dev/null +++ b/infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep @@ -0,0 +1,22 @@ +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/infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep b/infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep new file mode 100644 index 0000000..2c9688e --- /dev/null +++ b/infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep @@ -0,0 +1,46 @@ +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/infra/core/database/cosmos/sql/cosmos-sql-account.bicep b/infra/core/database/cosmos/sql/cosmos-sql-account.bicep new file mode 100644 index 0000000..e8b030f --- /dev/null +++ b/infra/core/database/cosmos/sql/cosmos-sql-account.bicep @@ -0,0 +1,21 @@ +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/infra/core/database/cosmos/sql/cosmos-sql-db.bicep b/infra/core/database/cosmos/sql/cosmos-sql-db.bicep new file mode 100644 index 0000000..5a4de20 --- /dev/null +++ b/infra/core/database/cosmos/sql/cosmos-sql-db.bicep @@ -0,0 +1,73 @@ +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 roleDefintion '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: roleDefintion.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 = roleDefintion.outputs.id diff --git a/infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep b/infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep new file mode 100644 index 0000000..6855edf --- /dev/null +++ b/infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep @@ -0,0 +1,18 @@ +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/infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep b/infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep new file mode 100644 index 0000000..cfb4033 --- /dev/null +++ b/infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep @@ -0,0 +1,29 @@ +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/infra/core/database/sqlserver/sqlserver.bicep b/infra/core/database/sqlserver/sqlserver.bicep new file mode 100644 index 0000000..821a908 --- /dev/null +++ b/infra/core/database/sqlserver/sqlserver.bicep @@ -0,0 +1,129 @@ +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 ${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/infra/core/database/sqlserver1.bicep b/infra/core/database/sqlserver/sqlserver1.bicep similarity index 76% rename from infra/core/database/sqlserver1.bicep rename to infra/core/database/sqlserver/sqlserver1.bicep index 671371c..891aa45 100644 --- a/infra/core/database/sqlserver1.bicep +++ b/infra/core/database/sqlserver/sqlserver1.bicep @@ -1,23 +1,20 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param appUser string = 'appUser' -param dbName string +param databaseName string param keyVaultName string param sqlAdmin string = 'sqlAdmin' -param sqlConnectionStringKey string = 'AZURE-SQL-CATALOG-CONNECTION-STRING' +param connectionStringKey string = 'AZURE-SQL-CATALOG-CONNECTION-STRING' @secure() param sqlAdminPassword string @secure() param appUserPassword string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - -resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { - name: '${abbrs.sqlServers}${resourceToken}-Catalog' +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name location: location tags: tags properties: { @@ -29,7 +26,7 @@ resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { } resource database 'databases' = { - name: dbName + name: databaseName location: location } @@ -46,7 +43,7 @@ resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { } resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { - name: 'script-${resourceToken}-Catalog' + name: '${name}-deployment-script' location: location kind: 'AzureCLI' properties: { @@ -65,7 +62,7 @@ resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' } { name: 'DBNAME' - value: dbName + value: databaseName } { name: 'DBSERVER' @@ -117,9 +114,9 @@ resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { parent: keyVault - name: sqlConnectionStringKey + name: connectionStringKey properties: { - value: '${azureSqlConnectionString}; Password=${appUserPassword}' + value: '${connectionString}; Password=${appUserPassword}' } } @@ -127,5 +124,6 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { name: keyVaultName } -var azureSqlConnectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' -output sqlConnectionStringKey string = sqlConnectionStringKey +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/infra/core/database/sqlserver2.bicep b/infra/core/database/sqlserver/sqlserver2.bicep similarity index 76% rename from infra/core/database/sqlserver2.bicep rename to infra/core/database/sqlserver/sqlserver2.bicep index 430f48f..305c97b 100644 --- a/infra/core/database/sqlserver2.bicep +++ b/infra/core/database/sqlserver/sqlserver2.bicep @@ -1,23 +1,20 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param appUser string = 'appUser' -param dbName string +param databaseName string param keyVaultName string param sqlAdmin string = 'sqlAdmin' -param sqlConnectionStringKey string = 'AZURE-SQL-IDENTITY-CONNECTION-STRING' +param connectionStringKey string = 'AZURE-SQL-IDENTITY-CONNECTION-STRING' @secure() param sqlAdminPassword string @secure() param appUserPassword string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - -resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { - name: '${abbrs.sqlServers}${resourceToken}-Identity' +resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { + name: name location: location tags: tags properties: { @@ -29,7 +26,7 @@ resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { } resource database 'databases' = { - name: dbName + name: databaseName location: location } @@ -46,7 +43,7 @@ resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = { } resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { - name: 'script-${resourceToken}-Identity' + name: '${name}-deployment-script' location: location kind: 'AzureCLI' properties: { @@ -65,7 +62,7 @@ resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' } { name: 'DBNAME' - value: dbName + value: databaseName } { name: 'DBSERVER' @@ -117,9 +114,9 @@ resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { parent: keyVault - name: sqlConnectionStringKey + name: connectionStringKey properties: { - value: '${azureSqlConnectionString}; Password=${appUserPassword}' + value: '${connectionString}; Password=${appUserPassword}' } } @@ -127,5 +124,6 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { name: keyVaultName } -var azureSqlConnectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' -output sqlConnectionStringKey string = sqlConnectionStringKey +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/infra/core/host/appservice-config-cosmos.bicep b/infra/core/host/appservice-config-cosmos.bicep deleted file mode 100644 index 65e91f9..0000000 --- a/infra/core/host/appservice-config-cosmos.bicep +++ /dev/null @@ -1,18 +0,0 @@ -param appServiceName string -param cosmosConnectionStringKey string = '' -param cosmosDatabaseName string = '' -param cosmosEndpoint string = '' - -module appServiceConfigCosmosSettings 'appservice-config-union.bicep' = { - name: '${appServiceName}-appservice-config-cosmos-settings' - params: { - appServiceName: appServiceName - configName: 'appsettings' - currentConfigProperties: list(resourceId('Microsoft.Web/sites/config', appServiceName, 'appsettings'), '2022-03-01').properties - additionalConfigProperties: { - AZURE_COSMOS_CONNECTION_STRING_KEY: cosmosConnectionStringKey - AZURE_COSMOS_DATABASE_NAME: cosmosDatabaseName - AZURE_COSMOS_ENDPOINT: cosmosEndpoint - } - } -} diff --git a/infra/core/host/appservice-config-logs.bicep b/infra/core/host/appservice-config-logs.bicep deleted file mode 100644 index fde8940..0000000 --- a/infra/core/host/appservice-config-logs.bicep +++ /dev/null @@ -1,11 +0,0 @@ -param appServiceName string - -resource siteConfigLogs 'Microsoft.Web/sites/config@2022-03-01' = { - name: '${appServiceName}/logs' - properties: { - applicationLogs: { fileSystem: { level: 'Verbose' } } - detailedErrorMessages: { enabled: true } - failedRequestsTracing: { enabled: true } - httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } - } -} diff --git a/infra/core/host/appservice-config-sqlserver.bicep b/infra/core/host/appservice-config-sqlserver.bicep deleted file mode 100644 index 3d8c8d0..0000000 --- a/infra/core/host/appservice-config-sqlserver.bicep +++ /dev/null @@ -1,14 +0,0 @@ -param appServiceName string -param sqlConnectionStringKey string - -module appServiceConfigSqlServerSettings 'appservice-config-union.bicep' = { - name: '${appServiceName}-appservice-config-sqlserver-settings' - params: { - appServiceName: appServiceName - configName: 'appsettings' - currentConfigProperties: list(resourceId('Microsoft.Web/sites/config', appServiceName, 'appsettings'), '2022-03-01').properties - additionalConfigProperties: { - AZURE_SQL_CONNECTION_STRING_KEY: sqlConnectionStringKey - } - } -} diff --git a/infra/core/host/appservice-config-union.bicep b/infra/core/host/appservice-config-union.bicep deleted file mode 100644 index 3e0bcee..0000000 --- a/infra/core/host/appservice-config-union.bicep +++ /dev/null @@ -1,9 +0,0 @@ -param additionalConfigProperties object -param appServiceName string -param configName string -param currentConfigProperties object - -resource siteConfigUnion 'Microsoft.Web/sites/config@2022-03-01' = { - name: '${appServiceName}/${configName}' - properties: union(currentConfigProperties, additionalConfigProperties) -} diff --git a/infra/core/host/appservice-dotnet.bicep b/infra/core/host/appservice-dotnet.bicep deleted file mode 100644 index 012a452..0000000 --- a/infra/core/host/appservice-dotnet.bicep +++ /dev/null @@ -1,35 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param appCommandLine string = '' -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'DOTNETCORE|6.0' -param managedIdentity bool = !(empty(keyVaultName)) -param scmDoBuildDuringDeployment bool = false -param serviceName string - -module appService 'appservice.bicep' = { - name: '${serviceName}-appservice-dotnet' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - appCommandLine: appCommandLine - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - scmDoBuildDuringDeployment: scmDoBuildDuringDeployment - serviceName: serviceName - } -} - -output identityPrincipalId string = appService.outputs.identityPrincipalId -output name string = appService.outputs.name -output uri string = appService.outputs.uri diff --git a/infra/core/host/appservice-node.bicep b/infra/core/host/appservice-node.bicep deleted file mode 100644 index 6f56d46..0000000 --- a/infra/core/host/appservice-node.bicep +++ /dev/null @@ -1,35 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param appCommandLine string = '' -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'NODE|16-lts' -param managedIdentity bool = !(empty(keyVaultName)) -param scmDoBuildDuringDeployment bool = false -param serviceName string - -module appService 'appservice.bicep' = { - name: '${serviceName}-appservice-node' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - appCommandLine: appCommandLine - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - scmDoBuildDuringDeployment: scmDoBuildDuringDeployment - serviceName: serviceName - } -} - -output identityPrincipalId string = appService.outputs.identityPrincipalId -output name string = appService.outputs.name -output uri string = appService.outputs.uri diff --git a/infra/core/host/appservice-python.bicep b/infra/core/host/appservice-python.bicep deleted file mode 100644 index 767c127..0000000 --- a/infra/core/host/appservice-python.bicep +++ /dev/null @@ -1,35 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param appCommandLine string = '' -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'PYTHON|3.8' -param managedIdentity bool = !(empty(keyVaultName)) -param scmDoBuildDuringDeployment bool = true -param serviceName string - -module appService 'appservice.bicep' = { - name: '${serviceName}-appservice-python' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - appCommandLine: appCommandLine - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - scmDoBuildDuringDeployment: scmDoBuildDuringDeployment - serviceName: serviceName - } -} - -output identityPrincipalId string = appService.outputs.identityPrincipalId -output name string = appService.outputs.name -output uri string = appService.outputs.uri diff --git a/infra/core/host/appservice.bicep b/infra/core/host/appservice.bicep index 3eece63..62e34a6 100644 --- a/infra/core/host/appservice.bicep +++ b/infra/core/host/appservice.bicep @@ -1,34 +1,42 @@ -param environmentName string +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 = '' -param applicationInsightsName string = '' -param appServicePlanId string param appSettings object = {} param clientAffinityEnabled bool = false +param enableOryxBuild bool = contains(kind, 'linux') param functionAppScaleLimit int = -1 -param keyVaultName string = '' -param kind string = 'app,linux' -param linuxFxVersion string = '' -param managedIdentity bool = !(empty(keyVaultName)) +param linuxFxVersion string = runtimeNameAndVersion param minimumElasticInstanceCount int = -1 param numberOfWorkers int = -1 param scmDoBuildDuringDeployment bool = false -param serviceName string param use32BitWorkerProcess bool = false -var abbrs = loadJsonContent('../../abbreviations.json') -var tags = { 'azd-env-name': environmentName } -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) - -var prefix = contains(kind, 'function') ? abbrs.webSitesFunctions : abbrs.webSitesAppService - resource appService 'Microsoft.Web/sites@2022-03-01' = { - name: '${prefix}${serviceName}-${resourceToken}' + name: name location: location - tags: union(tags, { 'azd-service-name': serviceName }) + tags: tags kind: kind properties: { serverFarmId: appServicePlanId @@ -49,41 +57,30 @@ resource appService 'Microsoft.Web/sites@2022-03-01' = { httpsOnly: true } - identity: managedIdentity ? { type: 'SystemAssigned' } : null + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } - resource appSettings 'config' = { + resource configAppSettings 'config' = { name: 'appsettings' - properties: union({ + properties: union(appSettings, + { SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) + ENABLE_ORYX_BUILD: string(enableOryxBuild) }, - !(empty(applicationInsightsName)) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, - !(empty(keyVaultName)) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) + !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, + !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) } -} -module appSettingsUnion 'appservice-config-union.bicep' = if (!empty(appSettings)) { - name: '${serviceName}-app-settings-union' - params: { - appServiceName: appService.name - configName: 'appsettings' - currentConfigProperties: appService::appSettings.list().properties - additionalConfigProperties: appSettings - } -} - -module siteConfigLogs 'appservice-config-logs.bicep' = { - name: '${serviceName}-appservice-config-logs' - params: { - appServiceName: appService.name - } -} - -module keyVaultAccess '../security/keyvault-access.bicep' = if (!(empty(keyVaultName))) { - name: '${serviceName}-appservice-keyvault-access' - params: { - principalId: appService.identity.principalId - environmentName: environmentName - location: location + resource configLogs 'config' = { + name: 'logs' + properties: { + applicationLogs: { fileSystem: { level: 'Verbose' } } + detailedErrorMessages: { enabled: true } + failedRequestsTracing: { enabled: true } + httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } + } + dependsOn: [ + configAppSettings + ] } } @@ -91,7 +88,7 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty( name: keyVaultName } -resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!(empty(applicationInsightsName))) { +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { name: applicationInsightsName } diff --git a/infra/core/host/appserviceplan-functions.bicep b/infra/core/host/appserviceplan-functions.bicep deleted file mode 100644 index 6693e6b..0000000 --- a/infra/core/host/appserviceplan-functions.bicep +++ /dev/null @@ -1,21 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param sku object = { - name: 'Y1' - tier: 'Dynamic' - size: 'Y1' - family: 'Y' -} - -module appServicePlanFunctions 'appserviceplan.bicep' = { - name: 'appserviceplan-functions' - params: { - environmentName: environmentName - location: location - sku: sku - kind: 'functionapp' - } -} - -output appServicePlanId string = appServicePlanFunctions.outputs.appServicePlanId diff --git a/infra/core/host/appserviceplan-sites.bicep b/infra/core/host/appserviceplan-sites.bicep deleted file mode 100644 index bd35137..0000000 --- a/infra/core/host/appserviceplan-sites.bicep +++ /dev/null @@ -1,15 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param sku object = { name: 'B1' } - -module appServicePlanSites 'appserviceplan.bicep' = { - name: 'appserviceplan-sites' - params: { - environmentName: environmentName - location: location - sku: sku - } -} - -output appServicePlanId string = appServicePlanSites.outputs.appServicePlanId diff --git a/infra/core/host/appserviceplan.bicep b/infra/core/host/appserviceplan.bicep index c72fcbd..69c35d7 100644 --- a/infra/core/host/appserviceplan.bicep +++ b/infra/core/host/appserviceplan.bicep @@ -1,16 +1,13 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param kind string = '' param reserved bool = true param sku object -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { - name: '${abbrs.webServerFarms}${resourceToken}' + name: name location: location tags: tags sku: sku @@ -20,4 +17,4 @@ resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { } } -output appServicePlanId string = appServicePlan.id +output id string = appServicePlan.id diff --git a/infra/core/host/container-app.bicep b/infra/core/host/container-app.bicep index fdfcf67..dde1bab 100644 --- a/infra/core/host/container-app.bicep +++ b/infra/core/host/container-app.bicep @@ -1,25 +1,28 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param containerAppsEnvironmentName string = '' +param containerName string = 'main' param containerRegistryName string = '' param env array = [] param external bool = true param imageName string param keyVaultName string = '' -param managedIdentity bool = !(empty(keyVaultName)) +param managedIdentity bool = !empty(keyVaultName) param targetPort int = 80 -param serviceName string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } +@description('CPU cores allocated to a single container instance, e.g. 0.5') +param containerCpuCoreCount string = '0.5' + +@description('Memory allocated to a single container instance, e.g. 1Gi') +param containerMemory string = '1.0Gi' resource app 'Microsoft.App/containerApps@2022-03-01' = { - name: '${abbrs.appContainerApps}${serviceName}-${resourceToken}' + name: name location: location - tags: union(tags, { 'azd-service-name': serviceName }) - identity: managedIdentity ? { type: 'SystemAssigned' } : null + tags: tags + identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } properties: { managedEnvironmentId: containerAppsEnvironment.id configuration: { @@ -47,33 +50,28 @@ resource app 'Microsoft.App/containerApps@2022-03-01' = { containers: [ { image: imageName - name: 'main' + name: containerName env: env + resources: { + cpu: json(containerCpuCoreCount) + memory: containerMemory + } } ] } } } -module keyVaultAccess '../security/keyvault-access.bicep' = if (!(empty(keyVaultName))) { - name: '${serviceName}-appservice-keyvault-access' - params: { - environmentName: environmentName - location: location - keyVaultName: keyVaultName - principalId: app.identity.principalId - } -} - resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = { - name: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' + name: containerAppsEnvironmentName } // 2022-02-01-preview needed for anonymousPullEnabled resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { - name: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + name: containerRegistryName } output identityPrincipalId string = managedIdentity ? app.identity.principalId : '' +output imageName string = imageName output name string = app.name output uri string = 'https://${app.properties.configuration.ingress.fqdn}' diff --git a/infra/core/host/container-apps-environment.bicep b/infra/core/host/container-apps-environment.bicep index b1e0578..2dd858c 100644 --- a/infra/core/host/container-apps-environment.bicep +++ b/infra/core/host/container-apps-environment.bicep @@ -1,15 +1,11 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} -param containerAppsEnvironmentName string = '' param logAnalyticsWorkspaceName string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' = { - name: !empty(containerAppsEnvironmentName) ? containerAppsEnvironmentName : '${abbrs.appManagedEnvironments}${resourceToken}' + name: name location: location tags: tags properties: { @@ -23,8 +19,8 @@ resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' } } -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' existing = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { name: logAnalyticsWorkspaceName } -output containerAppsEnvironmentName string = containerAppsEnvironment.name +output name string = containerAppsEnvironment.name diff --git a/infra/core/host/container-apps.bicep b/infra/core/host/container-apps.bicep index a916c7f..395af70 100644 --- a/infra/core/host/container-apps.bicep +++ b/infra/core/host/container-apps.bicep @@ -1,30 +1,30 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param containerAppsEnvironmentName string = '' -param containerAppsGroupName string = 'app' param containerRegistryName string = '' param logAnalyticsWorkspaceName string = '' module containerAppsEnvironment 'container-apps-environment.bicep' = { - name: '${containerAppsGroupName}-container-apps-environment' + name: '${name}-container-apps-environment' params: { - environmentName: environmentName + name: containerAppsEnvironmentName location: location - containerAppsEnvironmentName: containerAppsEnvironmentName + tags: tags logAnalyticsWorkspaceName: logAnalyticsWorkspaceName } } module containerRegistry 'container-registry.bicep' = { - name: '${containerAppsGroupName}-container-registry' + name: '${name}-container-registry' params: { - environmentName: environmentName + name: containerRegistryName location: location - containerRegistryName: containerRegistryName + tags: tags } } -output containerAppsEnvironmentName string = containerAppsEnvironment.outputs.containerAppsEnvironmentName -output containerRegistryEndpoint string = containerRegistry.outputs.containerRegistryEndpoint -output containerRegistryName string = containerRegistry.outputs.containerRegistryName +output environmentName string = containerAppsEnvironment.outputs.name +output registryLoginServer string = containerRegistry.outputs.loginServer +output registryName string = containerRegistry.outputs.name diff --git a/infra/core/host/container-registry.bicep b/infra/core/host/container-registry.bicep index 115da50..01c3213 100644 --- a/infra/core/host/container-registry.bicep +++ b/infra/core/host/container-registry.bicep @@ -1,9 +1,9 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param adminUserEnabled bool = true param anonymousPullEnabled bool = false -param containerRegistryName string = '' param dataEndpointEnabled bool = false param encryption object = { status: 'disabled' @@ -11,17 +11,13 @@ param encryption object = { param networkRuleBypassOptions string = 'AzureServices' param publicNetworkAccess string = 'Enabled' param sku object = { - name: 'Standard' + name: 'Basic' } param zoneRedundancy string = 'Disabled' -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - // 2022-02-01-preview needed for anonymousPullEnabled resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { - name: !empty(containerRegistryName) ? containerRegistryName : '${abbrs.containerRegistryRegistries}${resourceToken}' + name: name location: location tags: tags sku: sku @@ -36,5 +32,5 @@ resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-pr } } -output containerRegistryEndpoint string = containerRegistry.properties.loginServer -output containerRegistryName string = containerRegistry.name +output loginServer string = containerRegistry.properties.loginServer +output name string = containerRegistry.name diff --git a/infra/core/host/functions-node.bicep b/infra/core/host/functions-node.bicep deleted file mode 100644 index e4731f4..0000000 --- a/infra/core/host/functions-node.bicep +++ /dev/null @@ -1,34 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'NODE|16' -param managedIdentity bool = !(empty(keyVaultName)) -param serviceName string -param storageAccountName string - -module functions 'functions.bicep' = { - name: '${serviceName}-functions-node' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - functionsWorkerRuntime: 'node' - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - serviceName: serviceName - storageAccountName: storageAccountName - } -} - -output identityPrincipalId string = functions.outputs.identityPrincipalId -output name string = functions.outputs.name -output uri string = functions.outputs.uri diff --git a/infra/core/host/functions-python.bicep b/infra/core/host/functions-python.bicep deleted file mode 100644 index 2ff79bd..0000000 --- a/infra/core/host/functions-python.bicep +++ /dev/null @@ -1,34 +0,0 @@ -param environmentName string -param location string = resourceGroup().location - -param allowedOrigins array = [] -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} -param keyVaultName string = '' -param linuxFxVersion string = 'PYTHON|3.8' -param managedIdentity bool = !(empty(keyVaultName)) -param serviceName string -param storageAccountName string - -module functions 'functions.bicep' = { - name: '${serviceName}-functions-python' - params: { - environmentName: environmentName - location: location - allowedOrigins: allowedOrigins - applicationInsightsName: applicationInsightsName - appServicePlanId: appServicePlanId - appSettings: appSettings - functionsWorkerRuntime: 'python' - keyVaultName: keyVaultName - linuxFxVersion: linuxFxVersion - managedIdentity: managedIdentity - serviceName: serviceName - storageAccountName: storageAccountName - } -} - -output identityPrincipalId string = functions.outputs.identityPrincipalId -output name string = functions.outputs.name -output uri string = functions.outputs.uri diff --git a/infra/core/host/functions.bicep b/infra/core/host/functions.bicep index 818485f..28a581b 100644 --- a/infra/core/host/functions.bicep +++ b/infra/core/host/functions.bicep @@ -1,41 +1,63 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} -param allowedOrigins array = [] -param alwaysOn bool = false +// 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 = '' param appSettings object = {} param clientAffinityEnabled bool = false -param functionAppScaleLimit int = 200 -param functionsExtensionVersion string = '~4' -param functionsWorkerRuntime string -param kind string = 'functionapp,linux' -param linuxFxVersion string = '' -param keyVaultName string = '' -param managedIdentity bool = !(empty(keyVaultName)) -param minimumElasticInstanceCount int = 0 -param numberOfWorkers int = 1 +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 serviceName string -param storageAccountName string param use32BitWorkerProcess bool = false module functions 'appservice.bicep' = { - name: '${serviceName}-functions' + name: '${name}-functions' params: { - environmentName: environmentName + 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: functionsExtensionVersion - FUNCTIONS_WORKER_RUNTIME: functionsWorkerRuntime + FUNCTIONS_EXTENSION_VERSION: extensionVersion + FUNCTIONS_WORKER_RUNTIME: runtimeName }) clientAffinityEnabled: clientAffinityEnabled + enableOryxBuild: enableOryxBuild functionAppScaleLimit: functionAppScaleLimit keyVaultName: keyVaultName kind: kind @@ -43,8 +65,10 @@ module functions 'appservice.bicep' = { managedIdentity: managedIdentity minimumElasticInstanceCount: minimumElasticInstanceCount numberOfWorkers: numberOfWorkers + runtimeName: runtimeName + runtimeVersion: runtimeVersion + runtimeNameAndVersion: runtimeNameAndVersion scmDoBuildDuringDeployment: scmDoBuildDuringDeployment - serviceName: serviceName use32BitWorkerProcess: use32BitWorkerProcess } } diff --git a/infra/core/host/staticwebapp.bicep b/infra/core/host/staticwebapp.bicep index 3537fa9..91c2d0d 100644 --- a/infra/core/host/staticwebapp.bicep +++ b/infra/core/host/staticwebapp.bicep @@ -1,20 +1,16 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} -param serviceName string param sku object = { name: 'Free' tier: 'Free' } -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource web 'Microsoft.Web/staticSites@2022-03-01' = { - name: '${abbrs.webStaticSites}${serviceName}-${resourceToken}' + name: name location: location - tags: union(tags, { 'azd-service-name': serviceName }) + tags: tags sku: sku properties: { provider: 'Custom' diff --git a/infra/core/monitor/applicationinsights-dashboard.bicep b/infra/core/monitor/applicationinsights-dashboard.bicep index 11a6512..b7af2c1 100644 --- a/infra/core/monitor/applicationinsights-dashboard.bicep +++ b/infra/core/monitor/applicationinsights-dashboard.bicep @@ -1,14 +1,11 @@ -param environmentName string -param location string = resourceGroup().location +param name string param applicationInsightsName string - -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } +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: '${abbrs.portalDashboards}${resourceToken}' + name: name location: location tags: tags properties: { diff --git a/infra/core/monitor/applicationinsights.bicep b/infra/core/monitor/applicationinsights.bicep index b9ce1fd..f76b292 100644 --- a/infra/core/monitor/applicationinsights.bicep +++ b/infra/core/monitor/applicationinsights.bicep @@ -1,13 +1,12 @@ -param environmentName string +param name string +param dashboardName string param location string = resourceGroup().location +param tags object = {} + param logAnalyticsWorkspaceId string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { - name: '${abbrs.insightsComponents}${resourceToken}' + name: name location: location tags: tags kind: 'web' @@ -20,11 +19,12 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = { name: 'application-insights-dashboard' params: { - environmentName: environmentName + name: dashboardName location: location applicationInsightsName: applicationInsights.name } } -output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString -output applicationInsightsName string = applicationInsights.name +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/infra/core/monitor/loganalytics.bicep b/infra/core/monitor/loganalytics.bicep index a36912c..770544c 100644 --- a/infra/core/monitor/loganalytics.bicep +++ b/infra/core/monitor/loganalytics.bicep @@ -1,12 +1,9 @@ -param environmentName string +param name string param location string = resourceGroup().location - -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } +param tags object = {} resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { - name: '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + name: name location: location tags: tags properties: any({ @@ -20,5 +17,5 @@ resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-previ }) } -output logAnalyticsWorkspaceId string = logAnalytics.id -output logAnalyticsWorkspaceName string = logAnalytics.name +output id string = logAnalytics.id +output name string = logAnalytics.name diff --git a/infra/core/monitor/monitoring.bicep b/infra/core/monitor/monitoring.bicep index 1c5ae3e..96ba11e 100644 --- a/infra/core/monitor/monitoring.bicep +++ b/infra/core/monitor/monitoring.bicep @@ -1,24 +1,31 @@ -param environmentName string +param logAnalyticsName string +param applicationInsightsName string +param applicationInsightsDashboardName string param location string = resourceGroup().location +param tags object = {} module logAnalytics 'loganalytics.bicep' = { name: 'loganalytics' params: { - environmentName: environmentName + name: logAnalyticsName location: location + tags: tags } } module applicationInsights 'applicationinsights.bicep' = { name: 'applicationinsights' params: { - environmentName: environmentName + name: applicationInsightsName location: location - logAnalyticsWorkspaceId: logAnalytics.outputs.logAnalyticsWorkspaceId + tags: tags + dashboardName: applicationInsightsDashboardName + logAnalyticsWorkspaceId: logAnalytics.outputs.id } } -output applicationInsightsConnectionString string = applicationInsights.outputs.applicationInsightsConnectionString -output applicationInsightsName string = applicationInsights.outputs.applicationInsightsName -output logAnalyticsWorkspaceId string = logAnalytics.outputs.logAnalyticsWorkspaceId -output logAnalyticsWorkspaceName string = logAnalytics.outputs.logAnalyticsWorkspaceName +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/infra/core/security/keyvault-access.bicep b/infra/core/security/keyvault-access.bicep index 30c00f4..96c9cf7 100644 --- a/infra/core/security/keyvault-access.bicep +++ b/infra/core/security/keyvault-access.bicep @@ -1,16 +1,12 @@ -param environmentName string -param location string = resourceGroup().location +param name string = 'add' param keyVaultName string = '' param permissions object = { secrets: [ 'get', 'list' ] } param principalId string -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) - resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = { parent: keyVault - name: 'add' + name: name properties: { accessPolicies: [ { objectId: principalId @@ -21,5 +17,5 @@ resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-0 } resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { - name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' + name: keyVaultName } diff --git a/infra/core/security/keyvault.bicep b/infra/core/security/keyvault.bicep index 96b8e84..0eb4a86 100644 --- a/infra/core/security/keyvault.bicep +++ b/infra/core/security/keyvault.bicep @@ -1,15 +1,11 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} -param keyVaultName string = '' param principalId string = '' -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { - name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' + name: name location: location tags: tags properties: { @@ -25,5 +21,5 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { } } -output keyVaultEndpoint string = keyVault.properties.vaultUri -output keyVaultName string = keyVault.name +output endpoint string = keyVault.properties.vaultUri +output name string = keyVault.name diff --git a/infra/core/storage/storage-account.bicep b/infra/core/storage/storage-account.bicep index 7d2eb91..a41972c 100644 --- a/infra/core/storage/storage-account.bicep +++ b/infra/core/storage/storage-account.bicep @@ -1,17 +1,15 @@ -param environmentName string +param name string param location string = resourceGroup().location +param tags object = {} param allowBlobPublicAccess bool = false +param containers array = [] param kind string = 'StorageV2' param minimumTlsVersion string = 'TLS1_2' param sku object = { name: 'Standard_LRS' } -var abbrs = loadJsonContent('../../abbreviations.json') -var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) -var tags = { 'azd-env-name': environmentName } - -resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { - name: '${abbrs.storageStorageAccounts}${resourceToken}' +resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { + name: name location: location tags: tags kind: kind @@ -24,6 +22,17 @@ resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { defaultAction: 'Allow' } } + + resource blobServices 'blobServices' = if (!empty(containers)) { + name: 'default' + 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/infra/main.bicep b/infra/main.bicep index a47a36f..55f8ddd 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -9,6 +9,18 @@ param environmentName string @description('Primary location for all resources') param location string +// 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 resourceGroupName string = '' +param webServiceName string = '' +param sqlServer1Name string = 'sqlServer-catalog-01' +param sqlServer2Name string = 'sqlServer-identity-01' +param sqlDatabaseName string = '' +param appServicePlanName string = '' +param keyVaultName string = '' + @description('Id of the user or app to assign application roles') param principalId string = '' @@ -21,26 +33,86 @@ param sqlAdminPassword string param appUserPassword string var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) var tags = { 'azd-env-name': environmentName } +// Organize resources in a resource group resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { - name: '${abbrs.resourcesResourceGroups}${environmentName}' + name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}' location: location tags: tags } -module resources 'resources.bicep' = { - name: 'resources' +// The application frontend +module web './app/web.bicep' = { + name: 'web' scope: rg params: { - environmentName: environmentName + name: !empty(webServiceName) ? webServiceName : '${abbrs.webSitesAppService}web-${resourceToken}' location: location - principalId: principalId + tags: tags + appServicePlanId: appServicePlan.outputs.id + } +} + +// The application database: Catalog +module sqlServer1 './app/catalog-db.bicep' = { + name: 'sql-catalog' + scope: rg + params: { + name: !empty(sqlServer1Name) ? sqlServer1Name : '${abbrs.sqlServers}${resourceToken}' + databaseName: sqlDatabaseName + location: location + tags: tags sqlAdminPassword: sqlAdminPassword appUserPassword: appUserPassword + keyVaultName: keyVault.outputs.name + } +} + +// The application database: Identity +module sqlServer2 './app/identity-db.bicep' = { + name: 'sql-identity' + scope: rg + params: { + name: !empty(sqlServer2Name) ? sqlServer2Name : '${abbrs.sqlServers}${resourceToken}' + databaseName: sqlDatabaseName + location: location + tags: tags + sqlAdminPassword: sqlAdminPassword + appUserPassword: appUserPassword + keyVaultName: keyVault.outputs.name + } +} + + + +// Store secrets in a keyvault +module keyVault './core/security/keyvault.bicep' = { + name: 'keyvault' + scope: rg + params: { + name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}' + location: location + tags: tags + principalId: principalId + } +} + + +// Create an App Service Plan to group applications under the same payment plan and SKU +module appServicePlan './core/host/appserviceplan.bicep' = { + name: 'appserviceplan' + scope: rg + params: { + name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}' + location: location + tags: tags + sku: { + name: 'B1' + } } } output AZURE_LOCATION string = location output AZURE_TENANT_ID string = tenant().tenantId -output REACT_APP_WEB_BASE_URL string = resources.outputs.WEB_URI diff --git a/infra/resources.bicep b/infra/resources.bicep deleted file mode 100644 index 0eafb44..0000000 --- a/infra/resources.bicep +++ /dev/null @@ -1,82 +0,0 @@ -param environmentName string -param location string = resourceGroup().location -param principalId string = '' - -@secure() -param sqlAdminPassword string - -@secure() -param appUserPassword string - -// The application frontend -module web './app/web.bicep' = { - name: 'web' - params: { - environmentName: environmentName - location: location - appServicePlanId: appServicePlan.outputs.appServicePlanId - } -} - -// The application database: Catalog -module sqlServer1 './app/dbCatalog.bicep' = { - name: 'sqlCatalog' - params: { - environmentName: environmentName - location: location - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - keyVaultName: keyVault.outputs.keyVaultName - } -} - -// The application database: Identity -module sqlServer2 './app/dbIdentity.bicep' = { - name: 'sqlIdentity' - params: { - environmentName: environmentName - location: location - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - keyVaultName: keyVault.outputs.keyVaultName - } -} - -// Configure web to use sqlCatalog -module apiSqlServerConfig1 './core/host/appservice-config-sqlserver.bicep' = { - name: 'web-sqlserver-config-1' - params: { - appServiceName: web.outputs.WEB_NAME - sqlConnectionStringKey: sqlServer1.outputs.sqlConnectionStringKey - } -} - -// Configure web to use sqlIdentity -module apiSqlServerConfig2 './core/host/appservice-config-sqlserver.bicep' = { - name: 'web-sqlserver-config-2' - params: { - appServiceName: web.outputs.WEB_NAME - sqlConnectionStringKey: sqlServer2.outputs.sqlConnectionStringKey - } -} - -// Store secrets in a keyvault -module keyVault './core/security/keyvault.bicep' = { - name: 'keyvault' - params: { - environmentName: environmentName - location: location - principalId: principalId - } -} - -// Create an App Service Plan to group applications under the same payment plan and SKU -module appServicePlan './core/host/appserviceplan-sites.bicep' = { - name: 'appserviceplan' - params: { - environmentName: environmentName - location: location - } -} - -output WEB_URI string = web.outputs.WEB_URI From b2a3137f02a3b7ef96f59fd4e55504602620b870 Mon Sep 17 00:00:00 2001 From: zedy Date: Fri, 18 Nov 2022 10:27:19 +0800 Subject: [PATCH 04/39] Remove some invalid changes --- src/Web/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Web/appsettings.json b/src/Web/appsettings.json index 4f5c0ed..70989a6 100644 --- a/src/Web/appsettings.json +++ b/src/Web/appsettings.json @@ -4,8 +4,8 @@ "webBase": "https://localhost:44315/" }, "ConnectionStrings": { - "CatalogConnection": "Server=sqlserver-catalog-01.database.windows.net; Database=Todo; User=appUser; Password=fACZr419yzJ19tA", - "IdentityConnection": "Server=sqlserver-identity-01.database.windows.net; Database=Todo; User=appUser; Password=fACZr419yzJ19tA" + "CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;", + "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" }, "CatalogBaseUrl": "", "Logging": { From 45ad9ed83f854e6abb8127cbfa7711025ec276d5 Mon Sep 17 00:00:00 2001 From: zedy Date: Fri, 18 Nov 2022 13:53:38 +0800 Subject: [PATCH 05/39] add scripts folder and rename sqlserver --- infra/app/catalog-db.bicep | 2 +- infra/app/identity-db.bicep | 2 +- .../{sqlserver1.bicep => sqlserver-catalog.bicep} | 0 .../{sqlserver2.bicep => sqlserver-identity.bicep} | 0 scripts/setup-database.ps1 | 9 +++++++++ scripts/setup-database.sh | 12 ++++++++++++ 6 files changed, 23 insertions(+), 2 deletions(-) rename infra/core/database/sqlserver/{sqlserver1.bicep => sqlserver-catalog.bicep} (100%) rename infra/core/database/sqlserver/{sqlserver2.bicep => sqlserver-identity.bicep} (100%) create mode 100644 scripts/setup-database.ps1 create mode 100644 scripts/setup-database.sh diff --git a/infra/app/catalog-db.bicep b/infra/app/catalog-db.bicep index 631f138..3d40c45 100644 --- a/infra/app/catalog-db.bicep +++ b/infra/app/catalog-db.bicep @@ -14,7 +14,7 @@ param appUserPassword string var defaultDatabaseName = 'Todo' var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName -module sqlServer1 '../core/database/sqlserver/sqlserver1.bicep' = { +module sqlServer1 '../core/database/sqlserver/sqlserver-catalog.bicep' = { name: 'sqlServer01' params: { name: name diff --git a/infra/app/identity-db.bicep b/infra/app/identity-db.bicep index 1673c53..9717032 100644 --- a/infra/app/identity-db.bicep +++ b/infra/app/identity-db.bicep @@ -14,7 +14,7 @@ param appUserPassword string var defaultDatabaseName = 'Todo' var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName -module sqlServer2 '../core/database/sqlserver/sqlserver2.bicep' = { +module sqlServer2 '../core/database/sqlserver/sqlserver-identity.bicep' = { name: 'sqlServer02' params: { name: name diff --git a/infra/core/database/sqlserver/sqlserver1.bicep b/infra/core/database/sqlserver/sqlserver-catalog.bicep similarity index 100% rename from infra/core/database/sqlserver/sqlserver1.bicep rename to infra/core/database/sqlserver/sqlserver-catalog.bicep diff --git a/infra/core/database/sqlserver/sqlserver2.bicep b/infra/core/database/sqlserver/sqlserver-identity.bicep similarity index 100% rename from infra/core/database/sqlserver/sqlserver2.bicep rename to infra/core/database/sqlserver/sqlserver-identity.bicep diff --git a/scripts/setup-database.ps1 b/scripts/setup-database.ps1 new file mode 100644 index 0000000..8969760 --- /dev/null +++ b/scripts/setup-database.ps1 @@ -0,0 +1,9 @@ +# Default to current directory +$FOLDER_PATH = [string](Get-Location) + +# Before setting up your database, make sure of two things: +# 1. Ensure your connection strings in appsettings.json point to a local SQL Server instance. +# 2. Ensure the tool EF was already installed. +dotnet tool update --global dotnet-ef +dotnet ef database update -c catalogcontext -p $FOLDER_PATH/src/Infrastructure/Infrastructure.csproj -s $FOLDER_PATH/src/Web/Web.csproj +dotnet ef database update -c appidentitydbcontext -p $FOLDER_PATH/src/Infrastructure/Infrastructure.csproj -s $FOLDER_PATH/src/Web/Web.csproj \ No newline at end of file diff --git a/scripts/setup-database.sh b/scripts/setup-database.sh new file mode 100644 index 0000000..abd4e60 --- /dev/null +++ b/scripts/setup-database.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Default to current directory +FOLDER_PATH=$(pwd) + + +# Before setting up your database, make sure of two things: +# 1. Ensure your connection strings in appsettings.json point to a local SQL Server instance. +# 2. Ensure the tool EF was already installed. +dotnet tool update --global dotnet-ef +dotnet ef database update -c catalogcontext -p $FOLDER_PATH/src/Infrastructure/Infrastructure.csproj -s $FOLDER_PATH/src/Web/Web.csproj +dotnet ef database update -c appidentitydbcontext -p $FOLDER_PATH/src/Infrastructure/Infrastructure.csproj -s $FOLDER_PATH/src/Web/Web.csproj \ No newline at end of file From 5b86898d61bb075c75c26ffd07db8f82787f11a8 Mon Sep 17 00:00:00 2001 From: Jon Gallant Date: Fri, 9 Dec 2022 16:03:04 -0800 Subject: [PATCH 06/39] Update bicep add azure.yaml scripts --- azure.yaml | 13 +- infra/app/catalog-db.bicep | 31 ----- infra/app/identity-db.bicep | 31 ----- infra/app/web.bicep | 23 ---- .../sqlserver/sqlserver-catalog.bicep | 129 ------------------ .../sqlserver/sqlserver-identity.bicep | 129 ------------------ infra/main.bicep | 30 ++-- 7 files changed, 24 insertions(+), 362 deletions(-) delete mode 100644 infra/app/catalog-db.bicep delete mode 100644 infra/app/identity-db.bicep delete mode 100644 infra/app/web.bicep delete mode 100644 infra/core/database/sqlserver/sqlserver-catalog.bicep delete mode 100644 infra/core/database/sqlserver/sqlserver-identity.bicep diff --git a/azure.yaml b/azure.yaml index 9590449..f36e91e 100644 --- a/azure.yaml +++ b/azure.yaml @@ -1,8 +1,11 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/wbreza/azure-dev/main/schemas/v1.0/azure.yaml.json name: eShopOnWeb services: - web: - project: ./src/Web - language: csharp - host: appservice + web: + project: ./src/Web + language: csharp + host: appservice +scripts: + postprovision: + path: './scripts/setup-database.ps1' \ No newline at end of file diff --git a/infra/app/catalog-db.bicep b/infra/app/catalog-db.bicep deleted file mode 100644 index 3d40c45..0000000 --- a/infra/app/catalog-db.bicep +++ /dev/null @@ -1,31 +0,0 @@ -param name string -param location string = resourceGroup().location -param tags object = {} - -param databaseName string = 'CatalogDB' -param keyVaultName string - -@secure() -param sqlAdminPassword string -@secure() -param appUserPassword 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 sqlServer1 '../core/database/sqlserver/sqlserver-catalog.bicep' = { - name: 'sqlServer01' - params: { - name: name - location: location - tags: tags - databaseName: actualDatabaseName - keyVaultName: keyVaultName - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - } -} - -output sqlCatalogConnectionStringKey string = sqlServer1.outputs.connectionStringKey -output sqlCatalogDatabase1Name string = sqlServer1.outputs.databaseName diff --git a/infra/app/identity-db.bicep b/infra/app/identity-db.bicep deleted file mode 100644 index 9717032..0000000 --- a/infra/app/identity-db.bicep +++ /dev/null @@ -1,31 +0,0 @@ -param name string -param location string = resourceGroup().location -param tags object = {} - -param databaseName string = 'IdentityDB' -param keyVaultName string - -@secure() -param sqlAdminPassword string -@secure() -param appUserPassword 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 sqlServer2 '../core/database/sqlserver/sqlserver-identity.bicep' = { - name: 'sqlServer02' - params: { - name: name - location: location - tags: tags - databaseName: actualDatabaseName - keyVaultName: keyVaultName - sqlAdminPassword: sqlAdminPassword - appUserPassword: appUserPassword - } -} - -output sqlCatalogConnectionStringKey string = sqlServer2.outputs.connectionStringKey -output sqlCatalogDatabase1Name string = sqlServer2.outputs.databaseName diff --git a/infra/app/web.bicep b/infra/app/web.bicep deleted file mode 100644 index f33a276..0000000 --- a/infra/app/web.bicep +++ /dev/null @@ -1,23 +0,0 @@ -param name string -param location string = resourceGroup().location -param tags object = {} -param serviceName string = 'web' -param appCommandLine string = 'pm2 serve /home/site/wwwroot --no-daemon --spa' -param applicationInsightsName string = '' -param appServicePlanId string -param appSettings object = {} - -module web '../core/host/appservice.bicep' = { - name: '${name}-deployment' - params: { - name: name - location: location - appServicePlanId: appServicePlanId - runtimeName: 'dotnetcore' - runtimeVersion: '6.0' - tags: union(tags, { 'azd-service-name': serviceName }) - scmDoBuildDuringDeployment: false - } -} - -output REACT_APP_WEB_BASE_URL string = web.outputs.uri diff --git a/infra/core/database/sqlserver/sqlserver-catalog.bicep b/infra/core/database/sqlserver/sqlserver-catalog.bicep deleted file mode 100644 index 891aa45..0000000 --- a/infra/core/database/sqlserver/sqlserver-catalog.bicep +++ /dev/null @@ -1,129 +0,0 @@ -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-CATALOG-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 ${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/infra/core/database/sqlserver/sqlserver-identity.bicep b/infra/core/database/sqlserver/sqlserver-identity.bicep deleted file mode 100644 index 305c97b..0000000 --- a/infra/core/database/sqlserver/sqlserver-identity.bicep +++ /dev/null @@ -1,129 +0,0 @@ -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-IDENTITY-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 ${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/infra/main.bicep b/infra/main.bicep index 55f8ddd..1d8bba8 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -15,9 +15,10 @@ param location string // } param resourceGroupName string = '' param webServiceName string = '' -param sqlServer1Name string = 'sqlServer-catalog-01' -param sqlServer2Name string = 'sqlServer-identity-01' -param sqlDatabaseName string = '' +param catalogDatabaseName string = '' +param catalogDatabaseServerName string = '' +param identityDatabaseName string = '' +param identityDatabaseServerName string = '' param appServicePlanName string = '' param keyVaultName string = '' @@ -44,49 +45,51 @@ resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { } // The application frontend -module web './app/web.bicep' = { +module web './core/host/appservice.bicep' = { name: 'web' scope: rg params: { name: !empty(webServiceName) ? webServiceName : '${abbrs.webSitesAppService}web-${resourceToken}' location: location - tags: tags appServicePlanId: appServicePlan.outputs.id + runtimeName: 'dotnetcore' + runtimeVersion: '6.0' + tags: union(tags, { 'azd-service-name': 'web' }) } } // The application database: Catalog -module sqlServer1 './app/catalog-db.bicep' = { +module catalogDb './core/database/sqlserver/sqlserver.bicep' = { name: 'sql-catalog' scope: rg params: { - name: !empty(sqlServer1Name) ? sqlServer1Name : '${abbrs.sqlServers}${resourceToken}' - databaseName: sqlDatabaseName + name: !empty(catalogDatabaseServerName) ? catalogDatabaseServerName : '${abbrs.sqlServers}catalog-${resourceToken}' + databaseName: catalogDatabaseName location: location tags: tags sqlAdminPassword: sqlAdminPassword appUserPassword: appUserPassword keyVaultName: keyVault.outputs.name + connectionStringKey: 'AZURE-SQL-CATALOG-CONNECTION-STRING' } } // The application database: Identity -module sqlServer2 './app/identity-db.bicep' = { +module identityDb './core/database/sqlserver/sqlserver.bicep' = { name: 'sql-identity' scope: rg params: { - name: !empty(sqlServer2Name) ? sqlServer2Name : '${abbrs.sqlServers}${resourceToken}' - databaseName: sqlDatabaseName + name: !empty(identityDatabaseServerName) ? identityDatabaseServerName : '${abbrs.sqlServers}identity-${resourceToken}' + databaseName: identityDatabaseName location: location tags: tags sqlAdminPassword: sqlAdminPassword appUserPassword: appUserPassword keyVaultName: keyVault.outputs.name + connectionStringKey: 'AZURE-SQL-IDENTITY-CONNECTION-STRING' } } - - // Store secrets in a keyvault module keyVault './core/security/keyvault.bicep' = { name: 'keyvault' @@ -99,7 +102,6 @@ module keyVault './core/security/keyvault.bicep' = { } } - // Create an App Service Plan to group applications under the same payment plan and SKU module appServicePlan './core/host/appserviceplan.bicep' = { name: 'appserviceplan' From 8697f4a082e4153aea1254b7217605864d089ae2 Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 12 Dec 2022 10:42:06 +0800 Subject: [PATCH 07/39] add azd in codespace --- .devcontainer/devcontainer.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6264a06..f146232 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,24 @@ "INSTALL_AZURE_CLI": "false" } }, - + "features": { + "ghcr.io/devcontainers/features/azure-cli:1": { + "version": "2.38" + }, + "ghcr.io/devcontainers/features/docker-from-docker:1": { + "version": "20.10" + }, + "ghcr.io/devcontainers/features/dotnet:1": { + "version": "6.0" + }, + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "2" + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "16", + "nodeGypDependencies": false + } + }, // Comment out to connect as root user. See https://aka.ms/vscode-remote/containers/non-root. // make sure this is the same as USERNAME above "remoteUser": "vscode", @@ -23,6 +40,7 @@ // Add the IDs of extensions you want installed when the container is created. "extensions": [ + "ms-azuretools.azure-dev", "ms-dotnettools.csharp", "formulahendry.dotnet-test-explorer", "ms-vscode.vscode-node-azure-pack", From a949d4b225c9c913eea5c9e8e80bed74653f32d8 Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 12 Dec 2022 10:56:23 +0800 Subject: [PATCH 08/39] upgrade dotnet core 5.0 to 6.0 in order to install dotnet-ef --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index d188b1d..f60cd45 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- -FROM mcr.microsoft.com/dotnet/sdk:5.0 +FROM mcr.microsoft.com/dotnet/sdk:6.0 # This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" # property in devcontainer.json to use it. On Linux, the container user's GID/UIDs From 7f40d2d115db4ee5e9be73b1d518e676ea3af2fe Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 12 Dec 2022 11:14:42 +0800 Subject: [PATCH 09/39] install azd in codespace --- .devcontainer/Dockerfile | 7 +++++++ .devcontainer/devcontainer.json | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f60cd45..05e5acb 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -20,6 +20,7 @@ ENV NVM_DIR=/usr/local/share/nvm # [Optional] Install the Azure CLI ARG INSTALL_AZURE_CLI="false" +ARG INSTALL_AZURE_DEVELOPER_CLI="true" # Configure apt and install packages RUN apt-get update \ @@ -68,6 +69,12 @@ RUN apt-get update \ && apt-get install -y azure-cli; \ fi \ # + # [Optional] Install the Azure Developer CLI + && if [ "$INSTALL_AZURE_DEVELOPER_CLI" = "true" ]; then \ + apt-get update \ + && curl -fsSL https://aka.ms/install-azd.sh | bash \ + fi \ + # # Install EF Core dotnet tool && dotnet tool install dotnet-ef --tool-path /home/$USERNAME/.dotnet/tools \ && chown -R $USERNAME /home/$USERNAME/.dotnet \ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f146232..3bee848 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,8 @@ "USERNAME": "vscode", "INSTALL_NODE": "false", "NODE_VERSION": "lts/*", - "INSTALL_AZURE_CLI": "false" + "INSTALL_AZURE_CLI": "false", + "INSTALL_AZURE_DEVELOPER_CLI": "true" } }, "features": { From 04b88ffbb84a7837c5d3f90b3727bd9dbeb75fff Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 12 Dec 2022 11:25:12 +0800 Subject: [PATCH 10/39] install azd in codespace --- .devcontainer/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 05e5acb..1403065 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -71,8 +71,7 @@ RUN apt-get update \ # # [Optional] Install the Azure Developer CLI && if [ "$INSTALL_AZURE_DEVELOPER_CLI" = "true" ]; then \ - apt-get update \ - && curl -fsSL https://aka.ms/install-azd.sh | bash \ + curl -fsSL https://aka.ms/install-azd.sh | bash \ fi \ # # Install EF Core dotnet tool From 5951b6d3c1655d5db491e2a76c556072a3024188 Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 12 Dec 2022 11:27:43 +0800 Subject: [PATCH 11/39] Remove install azd in codespace, check some errors --- .devcontainer/Dockerfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 1403065..ad9b25e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -69,11 +69,6 @@ RUN apt-get update \ && apt-get install -y azure-cli; \ fi \ # - # [Optional] Install the Azure Developer CLI - && if [ "$INSTALL_AZURE_DEVELOPER_CLI" = "true" ]; then \ - curl -fsSL https://aka.ms/install-azd.sh | bash \ - fi \ - # # Install EF Core dotnet tool && dotnet tool install dotnet-ef --tool-path /home/$USERNAME/.dotnet/tools \ && chown -R $USERNAME /home/$USERNAME/.dotnet \ From c3479b9abcb22160f2dda85ca0b744c8ea4537e8 Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 12 Dec 2022 11:33:57 +0800 Subject: [PATCH 12/39] Install azd in codespace, check some errors --- .devcontainer/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ad9b25e..279aebc 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -23,7 +23,8 @@ ARG INSTALL_AZURE_CLI="false" ARG INSTALL_AZURE_DEVELOPER_CLI="true" # Configure apt and install packages -RUN apt-get update \ +RUN curl -fsSL https://aka.ms/install-azd.sh | bash \ + && apt-get update \ && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \ # From e5c6f71155ba4a2c63f90e23bdad7783dfb6c7f5 Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 12 Dec 2022 14:57:57 +0800 Subject: [PATCH 13/39] eliminate manually update the appsettings.json file --- infra/core/database/sqlserver/sqlserver.bicep | 1 + infra/main.bicep | 8 ++++++-- src/Infrastructure/Dependencies.cs | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/infra/core/database/sqlserver/sqlserver.bicep b/infra/core/database/sqlserver/sqlserver.bicep index 821a908..1c4c212 100644 --- a/infra/core/database/sqlserver/sqlserver.bicep +++ b/infra/core/database/sqlserver/sqlserver.bicep @@ -127,3 +127,4 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' output connectionStringKey string = connectionStringKey output databaseName string = sqlServer::database.name +output connectionString string = connectionString diff --git a/infra/main.bicep b/infra/main.bicep index 1d8bba8..535ded5 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -15,9 +15,9 @@ param location string // } param resourceGroupName string = '' param webServiceName string = '' -param catalogDatabaseName string = '' +param catalogDatabaseName string = 'catalogDatabase' param catalogDatabaseServerName string = '' -param identityDatabaseName string = '' +param identityDatabaseName string = 'identityDatabase' param identityDatabaseServerName string = '' param appServicePlanName string = '' param keyVaultName string = '' @@ -55,6 +55,10 @@ module web './core/host/appservice.bicep' = { runtimeName: 'dotnetcore' runtimeVersion: '6.0' tags: union(tags, { 'azd-service-name': 'web' }) + appSettings: { + CATALOG_CONNECTION_STRING_VALUE: '${catalogDb.outputs.connectionString}; Password=${appUserPassword}' + IDENTITY_CONNECTION_STRING_VALUE: '${identityDb.outputs.connectionString}; Password=${appUserPassword}' + } } } diff --git a/src/Infrastructure/Dependencies.cs b/src/Infrastructure/Dependencies.cs index d049a96..d399531 100644 --- a/src/Infrastructure/Dependencies.cs +++ b/src/Infrastructure/Dependencies.cs @@ -30,11 +30,11 @@ public static class Dependencies // Requires LocalDB which can be installed with SQL Server Express 2016 // https://www.microsoft.com/en-us/download/details.aspx?id=54284 services.AddDbContext(c => - c.UseSqlServer(configuration.GetConnectionString("CatalogConnection"))); + c.UseSqlServer(configuration["CATALOG_CONNECTION_STRING_VALUE"])); // Add Identity DbContext services.AddDbContext(options => - options.UseSqlServer(configuration.GetConnectionString("IdentityConnection"))); + options.UseSqlServer(configuration["IDENTITY_CONNECTION_STRING_VALUE"])); } } } From eba96d82af59eb69ab6a9bdfbacd5a37850cf37f Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 12 Dec 2022 15:44:56 +0800 Subject: [PATCH 14/39] remove some invalid changes --- azure.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/azure.yaml b/azure.yaml index f36e91e..c63402c 100644 --- a/azure.yaml +++ b/azure.yaml @@ -5,7 +5,4 @@ services: web: project: ./src/Web language: csharp - host: appservice -scripts: - postprovision: - path: './scripts/setup-database.ps1' \ No newline at end of file + host: appservice \ No newline at end of file From b4853e22d312cc787e985886a0e44b4312e783c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenjie=20Yu=EF=BC=88MSFT=EF=BC=89?= <81678720+zedy-wj@users.noreply.github.com> Date: Tue, 13 Dec 2022 10:25:12 +0800 Subject: [PATCH 15/39] fix indent --- .devcontainer/devcontainer.json | 47 +++++++++++++-------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3bee848..375e927 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,61 +1,50 @@ // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.112.0/containers/dotnetcore-3.1 { - "name": "eShopOnWeb", + "name": "eShopOnWeb", "build": { "dockerfile": "Dockerfile", "args": { "USERNAME": "vscode", "INSTALL_NODE": "false", "NODE_VERSION": "lts/*", - "INSTALL_AZURE_CLI": "false", - "INSTALL_AZURE_DEVELOPER_CLI": "true" + "INSTALL_AZURE_CLI": "false" } }, "features": { - "ghcr.io/devcontainers/features/azure-cli:1": { - "version": "2.38" - }, "ghcr.io/devcontainers/features/docker-from-docker:1": { "version": "20.10" }, - "ghcr.io/devcontainers/features/dotnet:1": { - "version": "6.0" - }, "ghcr.io/devcontainers/features/github-cli:1": { "version": "2" - }, - "ghcr.io/devcontainers/features/node:1": { - "version": "16", - "nodeGypDependencies": false } }, - // Comment out to connect as root user. See https://aka.ms/vscode-remote/containers/non-root. - // make sure this is the same as USERNAME above - "remoteUser": "vscode", + // Comment out to connect as root user. See https://aka.ms/vscode-remote/containers/non-root. + // make sure this is the same as USERNAME above + "remoteUser": "vscode", - // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash" - }, + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-azuretools.azure-dev", + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-azuretools.azure-dev", "ms-dotnettools.csharp", "formulahendry.dotnet-test-explorer", "ms-vscode.vscode-node-azure-pack", "ms-kubernetes-tools.vscode-kubernetes-tools", "redhat.vscode-yaml" - ], + ], - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [5000, 5001], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [5000, 5001], - // [Optional] To reuse of your local HTTPS dev cert, first export it locally using this command: - // * Windows PowerShell: - // dotnet dev-certs https --trust; dotnet dev-certs https -ep "$env:USERPROFILE/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" + // [Optional] To reuse of your local HTTPS dev cert, first export it locally using this command: + // * Windows PowerShell: + // dotnet dev-certs https --trust; dotnet dev-certs https -ep "$env:USERPROFILE/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" // * macOS/Linux terminal: // dotnet dev-certs https --trust; dotnet dev-certs https -ep "${HOME}/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" // From a4f4d69cec252b2f095390603eb26c5fdadcae8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenjie=20Yu=EF=BC=88MSFT=EF=BC=89?= <81678720+zedy-wj@users.noreply.github.com> Date: Tue, 13 Dec 2022 10:30:30 +0800 Subject: [PATCH 16/39] Fix indent --- .devcontainer/devcontainer.json | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 375e927..e18a3aa 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -25,12 +25,12 @@ // Set *default* container specific settings.json values on container create. "settings": { - "terminal.integrated.shell.linux": "/bin/bash" + "terminal.integrated.shell.linux": "/bin/bash" }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "ms-azuretools.azure-dev", + "ms-azuretools.azure-dev", "ms-dotnettools.csharp", "formulahendry.dotnet-test-explorer", "ms-vscode.vscode-node-azure-pack", @@ -45,20 +45,20 @@ // [Optional] To reuse of your local HTTPS dev cert, first export it locally using this command: // * Windows PowerShell: // dotnet dev-certs https --trust; dotnet dev-certs https -ep "$env:USERPROFILE/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" - // * macOS/Linux terminal: - // dotnet dev-certs https --trust; dotnet dev-certs https -ep "${HOME}/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" - // - // Next, after running the command above, uncomment lines in the 'mounts' and 'remoteEnv' lines below, - // and open / rebuild the container so the settings take effect. - // - "mounts": [ - // "source=${env:HOME}${env:USERPROFILE}/.aspnet/https,target=/home/vscode/.aspnet/https,type=bind" - ], - "remoteEnv": { - // "ASPNETCORE_Kestrel__Certificates__Default__Password": "SecurePwdGoesHere", - // "ASPNETCORE_Kestrel__Certificates__Default__Path": "/home/vscode/.aspnet/https/aspnetapp.pfx", - } + // * macOS/Linux terminal: + // dotnet dev-certs https --trust; dotnet dev-certs https -ep "${HOME}/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" + // + // Next, after running the command above, uncomment lines in the 'mounts' and 'remoteEnv' lines below, + // and open / rebuild the container so the settings take effect. + // + "mounts": [ + // "source=${env:HOME}${env:USERPROFILE}/.aspnet/https,target=/home/vscode/.aspnet/https,type=bind" + ], + "remoteEnv": { + // "ASPNETCORE_Kestrel__Certificates__Default__Password": "SecurePwdGoesHere", + // "ASPNETCORE_Kestrel__Certificates__Default__Path": "/home/vscode/.aspnet/https/aspnetapp.pfx", + } - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "dotnet restore" + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "dotnet restore" } From 50a8268b2ec237ccd0acfe365be0824c5e9ef295 Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 13 Dec 2022 10:38:28 +0800 Subject: [PATCH 17/39] Remove invalid changes --- .devcontainer/Dockerfile | 1 - .../core/database/cosmos/cosmos-account.bicep | 48 - .../cosmos/mongo/cosmos-mongo-account.bicep | 22 - .../cosmos/mongo/cosmos-mongo-db.bicep | 46 - .../cosmos/sql/cosmos-sql-account.bicep | 21 - .../database/cosmos/sql/cosmos-sql-db.bicep | 73 - .../cosmos/sql/cosmos-sql-role-assign.bicep | 18 - .../cosmos/sql/cosmos-sql-role-def.bicep | 29 - infra/core/host/container-app.bicep | 77 - .../host/container-apps-environment.bicep | 26 - infra/core/host/container-apps.bicep | 30 - infra/core/host/container-registry.bicep | 36 - infra/core/host/functions.bicep | 82 -- infra/core/host/staticwebapp.bicep | 21 - .../applicationinsights-dashboard.bicep | 1235 ----------------- infra/core/monitor/applicationinsights.bicep | 30 - infra/core/monitor/loganalytics.bicep | 21 - infra/core/monitor/monitoring.bicep | 31 - infra/core/security/keyvault-access.bicep | 21 - infra/core/storage/storage-account.bicep | 38 - 20 files changed, 1906 deletions(-) delete mode 100644 infra/core/database/cosmos/cosmos-account.bicep delete mode 100644 infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep delete mode 100644 infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep delete mode 100644 infra/core/database/cosmos/sql/cosmos-sql-account.bicep delete mode 100644 infra/core/database/cosmos/sql/cosmos-sql-db.bicep delete mode 100644 infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep delete mode 100644 infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep delete mode 100644 infra/core/host/container-app.bicep delete mode 100644 infra/core/host/container-apps-environment.bicep delete mode 100644 infra/core/host/container-apps.bicep delete mode 100644 infra/core/host/container-registry.bicep delete mode 100644 infra/core/host/functions.bicep delete mode 100644 infra/core/host/staticwebapp.bicep delete mode 100644 infra/core/monitor/applicationinsights-dashboard.bicep delete mode 100644 infra/core/monitor/applicationinsights.bicep delete mode 100644 infra/core/monitor/loganalytics.bicep delete mode 100644 infra/core/monitor/monitoring.bicep delete mode 100644 infra/core/security/keyvault-access.bicep delete mode 100644 infra/core/storage/storage-account.bicep diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 279aebc..54d859f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -20,7 +20,6 @@ ENV NVM_DIR=/usr/local/share/nvm # [Optional] Install the Azure CLI ARG INSTALL_AZURE_CLI="false" -ARG INSTALL_AZURE_DEVELOPER_CLI="true" # Configure apt and install packages RUN curl -fsSL https://aka.ms/install-azd.sh | bash \ diff --git a/infra/core/database/cosmos/cosmos-account.bicep b/infra/core/database/cosmos/cosmos-account.bicep deleted file mode 100644 index 6bc1f2e..0000000 --- a/infra/core/database/cosmos/cosmos-account.bicep +++ /dev/null @@ -1,48 +0,0 @@ -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.0' } : {} - 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/infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep b/infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep deleted file mode 100644 index bd2a2b5..0000000 --- a/infra/core/database/cosmos/mongo/cosmos-mongo-account.bicep +++ /dev/null @@ -1,22 +0,0 @@ -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/infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep b/infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep deleted file mode 100644 index 2c9688e..0000000 --- a/infra/core/database/cosmos/mongo/cosmos-mongo-db.bicep +++ /dev/null @@ -1,46 +0,0 @@ -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/infra/core/database/cosmos/sql/cosmos-sql-account.bicep b/infra/core/database/cosmos/sql/cosmos-sql-account.bicep deleted file mode 100644 index e8b030f..0000000 --- a/infra/core/database/cosmos/sql/cosmos-sql-account.bicep +++ /dev/null @@ -1,21 +0,0 @@ -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/infra/core/database/cosmos/sql/cosmos-sql-db.bicep b/infra/core/database/cosmos/sql/cosmos-sql-db.bicep deleted file mode 100644 index 5a4de20..0000000 --- a/infra/core/database/cosmos/sql/cosmos-sql-db.bicep +++ /dev/null @@ -1,73 +0,0 @@ -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 roleDefintion '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: roleDefintion.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 = roleDefintion.outputs.id diff --git a/infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep b/infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep deleted file mode 100644 index 6855edf..0000000 --- a/infra/core/database/cosmos/sql/cosmos-sql-role-assign.bicep +++ /dev/null @@ -1,18 +0,0 @@ -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/infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep b/infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep deleted file mode 100644 index cfb4033..0000000 --- a/infra/core/database/cosmos/sql/cosmos-sql-role-def.bicep +++ /dev/null @@ -1,29 +0,0 @@ -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/infra/core/host/container-app.bicep b/infra/core/host/container-app.bicep deleted file mode 100644 index dde1bab..0000000 --- a/infra/core/host/container-app.bicep +++ /dev/null @@ -1,77 +0,0 @@ -param name string -param location string = resourceGroup().location -param tags object = {} - -param containerAppsEnvironmentName string = '' -param containerName string = 'main' -param containerRegistryName string = '' -param env array = [] -param external bool = true -param imageName string -param keyVaultName string = '' -param managedIdentity bool = !empty(keyVaultName) -param targetPort int = 80 - -@description('CPU cores allocated to a single container instance, e.g. 0.5') -param containerCpuCoreCount string = '0.5' - -@description('Memory allocated to a single container instance, e.g. 1Gi') -param containerMemory string = '1.0Gi' - -resource app 'Microsoft.App/containerApps@2022-03-01' = { - name: name - location: location - tags: tags - identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } - properties: { - managedEnvironmentId: containerAppsEnvironment.id - configuration: { - activeRevisionsMode: 'single' - ingress: { - external: external - targetPort: targetPort - transport: 'auto' - } - secrets: [ - { - name: 'registry-password' - value: containerRegistry.listCredentials().passwords[0].value - } - ] - registries: [ - { - server: '${containerRegistry.name}.azurecr.io' - username: containerRegistry.name - passwordSecretRef: 'registry-password' - } - ] - } - template: { - containers: [ - { - image: imageName - name: containerName - env: env - resources: { - cpu: json(containerCpuCoreCount) - memory: containerMemory - } - } - ] - } - } -} - -resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = { - name: containerAppsEnvironmentName -} - -// 2022-02-01-preview needed for anonymousPullEnabled -resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { - name: containerRegistryName -} - -output identityPrincipalId string = managedIdentity ? app.identity.principalId : '' -output imageName string = imageName -output name string = app.name -output uri string = 'https://${app.properties.configuration.ingress.fqdn}' diff --git a/infra/core/host/container-apps-environment.bicep b/infra/core/host/container-apps-environment.bicep deleted file mode 100644 index 2dd858c..0000000 --- a/infra/core/host/container-apps-environment.bicep +++ /dev/null @@ -1,26 +0,0 @@ -param name string -param location string = resourceGroup().location -param tags object = {} - -param logAnalyticsWorkspaceName string - -resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' = { - name: name - location: location - tags: tags - properties: { - appLogsConfiguration: { - destination: 'log-analytics' - logAnalyticsConfiguration: { - customerId: logAnalyticsWorkspace.properties.customerId - sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey - } - } - } -} - -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { - name: logAnalyticsWorkspaceName -} - -output name string = containerAppsEnvironment.name diff --git a/infra/core/host/container-apps.bicep b/infra/core/host/container-apps.bicep deleted file mode 100644 index 395af70..0000000 --- a/infra/core/host/container-apps.bicep +++ /dev/null @@ -1,30 +0,0 @@ -param name string -param location string = resourceGroup().location -param tags object = {} - -param containerAppsEnvironmentName string = '' -param containerRegistryName string = '' -param logAnalyticsWorkspaceName string = '' - -module containerAppsEnvironment 'container-apps-environment.bicep' = { - name: '${name}-container-apps-environment' - params: { - name: containerAppsEnvironmentName - location: location - tags: tags - logAnalyticsWorkspaceName: logAnalyticsWorkspaceName - } -} - -module containerRegistry 'container-registry.bicep' = { - name: '${name}-container-registry' - params: { - name: containerRegistryName - location: location - tags: tags - } -} - -output environmentName string = containerAppsEnvironment.outputs.name -output registryLoginServer string = containerRegistry.outputs.loginServer -output registryName string = containerRegistry.outputs.name diff --git a/infra/core/host/container-registry.bicep b/infra/core/host/container-registry.bicep deleted file mode 100644 index 01c3213..0000000 --- a/infra/core/host/container-registry.bicep +++ /dev/null @@ -1,36 +0,0 @@ -param name string -param location string = resourceGroup().location -param tags object = {} - -param adminUserEnabled bool = true -param anonymousPullEnabled bool = false -param dataEndpointEnabled bool = false -param encryption object = { - status: 'disabled' -} -param networkRuleBypassOptions string = 'AzureServices' -param publicNetworkAccess string = 'Enabled' -param sku object = { - name: 'Basic' -} -param zoneRedundancy string = 'Disabled' - -// 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 - } -} - -output loginServer string = containerRegistry.properties.loginServer -output name string = containerRegistry.name diff --git a/infra/core/host/functions.bicep b/infra/core/host/functions.bicep deleted file mode 100644 index 28a581b..0000000 --- a/infra/core/host/functions.bicep +++ /dev/null @@ -1,82 +0,0 @@ -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 = '' -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 - -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 - 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/infra/core/host/staticwebapp.bicep b/infra/core/host/staticwebapp.bicep deleted file mode 100644 index 91c2d0d..0000000 --- a/infra/core/host/staticwebapp.bicep +++ /dev/null @@ -1,21 +0,0 @@ -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/infra/core/monitor/applicationinsights-dashboard.bicep b/infra/core/monitor/applicationinsights-dashboard.bicep deleted file mode 100644 index b7af2c1..0000000 --- a/infra/core/monitor/applicationinsights-dashboard.bicep +++ /dev/null @@ -1,1235 +0,0 @@ -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/infra/core/monitor/applicationinsights.bicep b/infra/core/monitor/applicationinsights.bicep deleted file mode 100644 index f76b292..0000000 --- a/infra/core/monitor/applicationinsights.bicep +++ /dev/null @@ -1,30 +0,0 @@ -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' = { - 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/infra/core/monitor/loganalytics.bicep b/infra/core/monitor/loganalytics.bicep deleted file mode 100644 index 770544c..0000000 --- a/infra/core/monitor/loganalytics.bicep +++ /dev/null @@ -1,21 +0,0 @@ -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/infra/core/monitor/monitoring.bicep b/infra/core/monitor/monitoring.bicep deleted file mode 100644 index 96ba11e..0000000 --- a/infra/core/monitor/monitoring.bicep +++ /dev/null @@ -1,31 +0,0 @@ -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/infra/core/security/keyvault-access.bicep b/infra/core/security/keyvault-access.bicep deleted file mode 100644 index 96c9cf7..0000000 --- a/infra/core/security/keyvault-access.bicep +++ /dev/null @@ -1,21 +0,0 @@ -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/infra/core/storage/storage-account.bicep b/infra/core/storage/storage-account.bicep deleted file mode 100644 index a41972c..0000000 --- a/infra/core/storage/storage-account.bicep +++ /dev/null @@ -1,38 +0,0 @@ -param name string -param location string = resourceGroup().location -param tags object = {} - -param allowBlobPublicAccess bool = false -param containers array = [] -param kind string = 'StorageV2' -param minimumTlsVersion string = 'TLS1_2' -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: { - minimumTlsVersion: minimumTlsVersion - allowBlobPublicAccess: allowBlobPublicAccess - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Allow' - } - } - - resource blobServices 'blobServices' = if (!empty(containers)) { - name: 'default' - 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 From a4427bcbf9735b654c12d190767b30d7206f3870 Mon Sep 17 00:00:00 2001 From: zedy Date: Wed, 14 Dec 2022 15:32:14 +0800 Subject: [PATCH 18/39] Pull the connection string from key vault --- .devcontainer/devcontainer.json | 3 +++ .vscode/extensions.json | 3 ++- infra/main.bicep | 14 +++++++++++-- src/Infrastructure/Dependencies.cs | 26 ++++++++++++++++++++++-- src/Infrastructure/Infrastructure.csproj | 2 ++ 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e18a3aa..046068a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,6 +12,9 @@ } }, "features": { + "ghcr.io/devcontainers/features/azure-cli:1": { + "version": "2.38" + }, "ghcr.io/devcontainers/features/docker-from-docker:1": { "version": "20.10" }, diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d0663c2..680470c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,7 @@ "formulahendry.dotnet-test-explorer", "ms-vscode.vscode-node-azure-pack", "ms-kubernetes-tools.vscode-kubernetes-tools", - "redhat.vscode-yaml" + "redhat.vscode-yaml", + "ms-azuretools.azure-dev" ] } \ No newline at end of file diff --git a/infra/main.bicep b/infra/main.bicep index 535ded5..bdf877a 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -56,8 +56,9 @@ module web './core/host/appservice.bicep' = { runtimeVersion: '6.0' tags: union(tags, { 'azd-service-name': 'web' }) appSettings: { - CATALOG_CONNECTION_STRING_VALUE: '${catalogDb.outputs.connectionString}; Password=${appUserPassword}' - IDENTITY_CONNECTION_STRING_VALUE: '${identityDb.outputs.connectionString}; Password=${appUserPassword}' + CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING' + IDENTITY_CONNECTION_STRING_KEY: 'AZURE-SQL-IDENTITY-CONNECTION-STRING' + KEY_VAULT_ENDPOINT: keyVault.outputs.endpoint } } } @@ -120,5 +121,14 @@ module appServicePlan './core/host/appserviceplan.bicep' = { } } +// Data outputs +output AZURE_SQL_CATALOG_CONNECTION_STRING string = catalogDb.outputs.connectionStringKey +output AZURE_SQL_IDENTITY_CONNECTION_STRING string = identityDb.outputs.connectionStringKey +output AZURE_SQL_CATALOG_DATABASE_NAME string = catalogDb.outputs.databaseName +output AZURE_SQL_IDENTITY_DATABASE_NAME string = identityDb.outputs.databaseName + +// App outputs output AZURE_LOCATION string = location output AZURE_TENANT_ID string = tenant().tenantId +output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.endpoint +output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name diff --git a/src/Infrastructure/Dependencies.cs b/src/Infrastructure/Dependencies.cs index d399531..ac319d2 100644 --- a/src/Infrastructure/Dependencies.cs +++ b/src/Infrastructure/Dependencies.cs @@ -3,6 +3,9 @@ using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Azure.Security.KeyVault.Secrets; +using Azure.Identity; +using System; namespace Microsoft.eShopWeb.Infrastructure; @@ -11,6 +14,12 @@ public static class Dependencies public static void ConfigureServices(IConfiguration configuration, IServiceCollection services) { var useOnlyInMemoryDatabase = false; + string keyVaultUri = configuration["KEY_VAULT_ENDPOINT"]; + string catalogConnectionStringKey = configuration["AZURE-SQL-CATALOG-CONNECTION-STRING"]; + string identityConnectionStringKey = configuration["AZURE-SQL-IDENTITY-CONNECTION-STRING"]; + string catalogConnectionStringValue = GetSqlConnectString(keyVaultUri, catalogConnectionStringKey); + string identityConnectionStringValue = GetSqlConnectString(keyVaultUri, identityConnectionStringKey); + if (configuration["UseOnlyInMemoryDatabase"] != null) { useOnlyInMemoryDatabase = bool.Parse(configuration["UseOnlyInMemoryDatabase"]); @@ -30,11 +39,24 @@ public static class Dependencies // Requires LocalDB which can be installed with SQL Server Express 2016 // https://www.microsoft.com/en-us/download/details.aspx?id=54284 services.AddDbContext(c => - c.UseSqlServer(configuration["CATALOG_CONNECTION_STRING_VALUE"])); + c.UseSqlServer(catalogConnectionStringValue)); // Add Identity DbContext services.AddDbContext(options => - options.UseSqlServer(configuration["IDENTITY_CONNECTION_STRING_VALUE"])); + options.UseSqlServer(identityConnectionStringValue)); } } + + public static string GetSqlConnectString(string keyVaultUri, string connectionStringKey) + { + if (connectionStringKey == null) + { + return ""; + } + + var secretClient = new SecretClient(new Uri(keyVaultUri), new ClientSecretCredential("","","")); + KeyVaultSecret secret = secretClient.GetSecret(connectionStringKey); + string secretValue = secret.Value; + return secretValue; + } } diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index a0be8bb..4c7a052 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -8,6 +8,8 @@ + + From 9e821608a1071460796a6e2989bd713e860e75f7 Mon Sep 17 00:00:00 2001 From: zedy Date: Wed, 14 Dec 2022 15:58:45 +0800 Subject: [PATCH 19/39] fix some bug --- infra/main.bicep | 6 +++--- src/Infrastructure/Dependencies.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index bdf877a..d1187f7 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -56,9 +56,9 @@ module web './core/host/appservice.bicep' = { runtimeVersion: '6.0' tags: union(tags, { 'azd-service-name': 'web' }) appSettings: { - CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING' - IDENTITY_CONNECTION_STRING_KEY: 'AZURE-SQL-IDENTITY-CONNECTION-STRING' - KEY_VAULT_ENDPOINT: keyVault.outputs.endpoint + AZURE_CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING' + AZURE_IDENTITY_CONNECTION_STRING_KEY: 'AZURE-SQL-IDENTITY-CONNECTION-STRING' + AZURE_KEY_VAULT_ENDPOINT: keyVault.outputs.endpoint } } } diff --git a/src/Infrastructure/Dependencies.cs b/src/Infrastructure/Dependencies.cs index ac319d2..7218310 100644 --- a/src/Infrastructure/Dependencies.cs +++ b/src/Infrastructure/Dependencies.cs @@ -14,9 +14,9 @@ public static class Dependencies public static void ConfigureServices(IConfiguration configuration, IServiceCollection services) { var useOnlyInMemoryDatabase = false; - string keyVaultUri = configuration["KEY_VAULT_ENDPOINT"]; - string catalogConnectionStringKey = configuration["AZURE-SQL-CATALOG-CONNECTION-STRING"]; - string identityConnectionStringKey = configuration["AZURE-SQL-IDENTITY-CONNECTION-STRING"]; + string keyVaultUri = configuration["AZURE_KEY_VAULT_ENDPOINT"]; + string catalogConnectionStringKey = configuration["AZURE_CATALOG_CONNECTION_STRING_KEY"]; + string identityConnectionStringKey = configuration["AZURE_IDENTITY_CONNECTION_STRING_KEY"]; string catalogConnectionStringValue = GetSqlConnectString(keyVaultUri, catalogConnectionStringKey); string identityConnectionStringValue = GetSqlConnectString(keyVaultUri, identityConnectionStringKey); From d4950760f21ac2cc42449a73c7101fcf5f16eb12 Mon Sep 17 00:00:00 2001 From: zedy Date: Thu, 15 Dec 2022 14:29:40 +0800 Subject: [PATCH 20/39] Remove some invalid changes --- infra/core/security/keyvault-access.bicep | 21 +++++++++++++++++++ infra/main.bicep | 18 ++++++++++++---- src/Infrastructure/Dependencies.cs | 25 ++--------------------- src/Infrastructure/Infrastructure.csproj | 2 -- 4 files changed, 37 insertions(+), 29 deletions(-) create mode 100644 infra/core/security/keyvault-access.bicep diff --git a/infra/core/security/keyvault-access.bicep b/infra/core/security/keyvault-access.bicep new file mode 100644 index 0000000..96c9cf7 --- /dev/null +++ b/infra/core/security/keyvault-access.bicep @@ -0,0 +1,21 @@ +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/infra/main.bicep b/infra/main.bicep index d1187f7..b8c5ebb 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -52,17 +52,27 @@ module web './core/host/appservice.bicep' = { name: !empty(webServiceName) ? webServiceName : '${abbrs.webSitesAppService}web-${resourceToken}' location: location appServicePlanId: appServicePlan.outputs.id + keyVaultName: keyVault.outputs.name runtimeName: 'dotnetcore' runtimeVersion: '6.0' tags: union(tags, { 'azd-service-name': 'web' }) appSettings: { - AZURE_CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING' - AZURE_IDENTITY_CONNECTION_STRING_KEY: 'AZURE-SQL-IDENTITY-CONNECTION-STRING' + AZURE_SQL_CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING' + AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY: 'AZURE-SQL-IDENTITY-CONNECTION-STRING' AZURE_KEY_VAULT_ENDPOINT: keyVault.outputs.endpoint } } } +module apiKeyVaultAccess './core/security/keyvault-access.bicep' = { + name: 'api-keyvault-access' + scope: rg + params: { + keyVaultName: keyVault.outputs.name + principalId: web.outputs.identityPrincipalId + } +} + // The application database: Catalog module catalogDb './core/database/sqlserver/sqlserver.bicep' = { name: 'sql-catalog' @@ -122,8 +132,8 @@ module appServicePlan './core/host/appserviceplan.bicep' = { } // Data outputs -output AZURE_SQL_CATALOG_CONNECTION_STRING string = catalogDb.outputs.connectionStringKey -output AZURE_SQL_IDENTITY_CONNECTION_STRING string = identityDb.outputs.connectionStringKey +output AZURE_SQL_CATALOG_CONNECTION_STRING_KEY string = catalogDb.outputs.connectionStringKey +output AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY string = identityDb.outputs.connectionStringKey output AZURE_SQL_CATALOG_DATABASE_NAME string = catalogDb.outputs.databaseName output AZURE_SQL_IDENTITY_DATABASE_NAME string = identityDb.outputs.databaseName diff --git a/src/Infrastructure/Dependencies.cs b/src/Infrastructure/Dependencies.cs index 7218310..954da48 100644 --- a/src/Infrastructure/Dependencies.cs +++ b/src/Infrastructure/Dependencies.cs @@ -3,9 +3,6 @@ using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Azure.Security.KeyVault.Secrets; -using Azure.Identity; -using System; namespace Microsoft.eShopWeb.Infrastructure; @@ -14,11 +11,6 @@ public static class Dependencies public static void ConfigureServices(IConfiguration configuration, IServiceCollection services) { var useOnlyInMemoryDatabase = false; - string keyVaultUri = configuration["AZURE_KEY_VAULT_ENDPOINT"]; - string catalogConnectionStringKey = configuration["AZURE_CATALOG_CONNECTION_STRING_KEY"]; - string identityConnectionStringKey = configuration["AZURE_IDENTITY_CONNECTION_STRING_KEY"]; - string catalogConnectionStringValue = GetSqlConnectString(keyVaultUri, catalogConnectionStringKey); - string identityConnectionStringValue = GetSqlConnectString(keyVaultUri, identityConnectionStringKey); if (configuration["UseOnlyInMemoryDatabase"] != null) { @@ -39,24 +31,11 @@ public static class Dependencies // Requires LocalDB which can be installed with SQL Server Express 2016 // https://www.microsoft.com/en-us/download/details.aspx?id=54284 services.AddDbContext(c => - c.UseSqlServer(catalogConnectionStringValue)); + c.UseSqlServer(configuration.GetConnectionString("CatalogConnection"))); // Add Identity DbContext services.AddDbContext(options => - options.UseSqlServer(identityConnectionStringValue)); + options.UseSqlServer(configuration.GetConnectionString("IdentityConnection"))); } } - - public static string GetSqlConnectString(string keyVaultUri, string connectionStringKey) - { - if (connectionStringKey == null) - { - return ""; - } - - var secretClient = new SecretClient(new Uri(keyVaultUri), new ClientSecretCredential("","","")); - KeyVaultSecret secret = secretClient.GetSecret(connectionStringKey); - string secretValue = secret.Value; - return secretValue; - } } diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 4c7a052..a0be8bb 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -8,8 +8,6 @@ - - From 9a13af426b2f6a9514c4ecfd814c4c1c187c7eaa Mon Sep 17 00:00:00 2001 From: zedy Date: Thu, 15 Dec 2022 15:41:24 +0800 Subject: [PATCH 21/39] Fix format and update the method of pulling connection string from kv with dac --- src/Infrastructure/Dependencies.cs | 1 - src/Web/AzureDeveloperCliCredential.cs | 148 +++++++++++++++++++++++++ src/Web/Program.cs | 16 ++- src/Web/Web.csproj | 2 + 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/Web/AzureDeveloperCliCredential.cs diff --git a/src/Infrastructure/Dependencies.cs b/src/Infrastructure/Dependencies.cs index 954da48..d049a96 100644 --- a/src/Infrastructure/Dependencies.cs +++ b/src/Infrastructure/Dependencies.cs @@ -11,7 +11,6 @@ public static class Dependencies public static void ConfigureServices(IConfiguration configuration, IServiceCollection services) { var useOnlyInMemoryDatabase = false; - if (configuration["UseOnlyInMemoryDatabase"] != null) { useOnlyInMemoryDatabase = bool.Parse(configuration["UseOnlyInMemoryDatabase"]); diff --git a/src/Web/AzureDeveloperCliCredential.cs b/src/Web/AzureDeveloperCliCredential.cs new file mode 100644 index 0000000..406c7bf --- /dev/null +++ b/src/Web/AzureDeveloperCliCredential.cs @@ -0,0 +1,148 @@ +using Azure.Core; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace Azure.Identity +{ + public class AzureDeveloperCliCredential : TokenCredential + { + internal const string AzdCliNotInstalled = $"Azure Developer CLI could not be found. {Troubleshoot}"; + internal const string AzdNotLogIn = "Please run 'azd login' from a command prompt to authenticate before using this credential."; + internal const string WinAzdCliError = "'azd is not recognized"; + internal const string AzdCliTimeoutError = "Azure Developer CLI authentication timed out."; + internal const string AzdCliFailedError = "Azure Developer CLI authentication failed due to an unknown error."; + internal const string Troubleshoot = "Please visit https://aka.ms/azure-dev for installation instructions and then, once installed, authenticate to your Azure account using 'azd login'."; + internal const string InteractiveLoginRequired = "Azure Developer CLI could not login. Interactive login is required."; + private const string RefreshTokeExpired = "The provided authorization code or refresh token has expired due to inactivity. Send a new interactive authorization request for this user and resource."; + + private static readonly string DefaultWorkingDirWindows = Environment.GetFolderPath(Environment.SpecialFolder.System); + private const string DefaultWorkingDirNonWindows = "/bin/"; + private static readonly string DefaultWorkingDir = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DefaultWorkingDirWindows : DefaultWorkingDirNonWindows; + + private static readonly Regex AzdNotFoundPattern = new Regex("azd:(.*)not found"); + + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken = default) + { + return RequestCliAccessTokenAsync(requestContext, cancellationToken) + .GetAwaiter() + .GetResult(); + } + + public override async ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken = default) + { + return await RequestCliAccessTokenAsync(requestContext, cancellationToken).ConfigureAwait(false); + } + + private async ValueTask RequestCliAccessTokenAsync(TokenRequestContext context, CancellationToken cancellationToken) + { + try + { + ProcessStartInfo processStartInfo = GetAzdCliProcessStartInfo(context.Scopes); + string output = await RunProcessAsync(processStartInfo); + + return DeserializeOutput(output); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + throw new AuthenticationFailedException(AzdCliTimeoutError); + } + catch (InvalidOperationException exception) + { + bool isWinError = exception.Message.StartsWith(WinAzdCliError, StringComparison.CurrentCultureIgnoreCase); + bool isOtherOsError = AzdNotFoundPattern.IsMatch(exception.Message); + + if (isWinError || isOtherOsError) + { + throw new CredentialUnavailableException(AzdCliNotInstalled); + } + + bool isAADSTSError = exception.Message.Contains("AADSTS"); + bool isLoginError = exception.Message.IndexOf("azd login", StringComparison.OrdinalIgnoreCase) != -1; + + if (isLoginError && !isAADSTSError) + { + throw new CredentialUnavailableException(AzdNotLogIn); + } + + bool isRefreshTokenFailedError = exception.Message.IndexOf(AzdCliFailedError, StringComparison.OrdinalIgnoreCase) != -1 && + exception.Message.IndexOf(RefreshTokeExpired, StringComparison.OrdinalIgnoreCase) != -1 || + exception.Message.IndexOf("CLIInternalError", StringComparison.OrdinalIgnoreCase) != -1; + + if (isRefreshTokenFailedError) + { + throw new CredentialUnavailableException(InteractiveLoginRequired); + } + + throw new AuthenticationFailedException($"{AzdCliFailedError} {Troubleshoot} {exception.Message}"); + } + catch (Exception ex) + { + throw new CredentialUnavailableException($"{AzdCliFailedError} {Troubleshoot} {ex.Message}"); + } + } + + private async ValueTask RunProcessAsync(ProcessStartInfo processStartInfo, CancellationToken cancellationToken = default) + { + var process = Process.Start(processStartInfo); + if (process == null) + { + throw new CredentialUnavailableException(AzdCliFailedError); + } + + await process.WaitForExitAsync(cancellationToken); + + if (process.ExitCode != 0) + { + var errorMessage = process.StandardError.ReadToEnd(); + throw new InvalidOperationException(errorMessage); + } + + return process.StandardOutput.ReadToEnd(); + } + + private ProcessStartInfo GetAzdCliProcessStartInfo(string[] scopes) + { + string scopeArgs = string.Join(" ", scopes.Select(scope => string.Format($"--scope {scope}"))); + string command = $"azd auth token --output json {scopeArgs}"; + + string fileName; + string argument; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"); + argument = $"/c \"{command}\""; + } + else + { + fileName = "/bin/sh"; + argument = $"-c \"{command}\""; + } + + return new ProcessStartInfo + { + FileName = fileName, + Arguments = argument, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + ErrorDialog = false, + CreateNoWindow = true, + WorkingDirectory = DefaultWorkingDir, + }; + } + + private static AccessToken DeserializeOutput(string output) + { + using JsonDocument document = JsonDocument.Parse(output); + + JsonElement root = document.RootElement; + string accessToken = root.GetProperty("token").GetString(); + DateTimeOffset expiresOn = root.GetProperty("expiresOn").GetDateTimeOffset(); + + return new AccessToken(accessToken, expiresOn); + } + } +} diff --git a/src/Web/Program.cs b/src/Web/Program.cs index e1181e2..a998b28 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -1,5 +1,6 @@ using System.Net.Mime; using Ardalis.ListStartupServices; +using Azure.Identity; using BlazorAdmin; using BlazorAdmin.Services; using Blazored.LocalStorage; @@ -8,6 +9,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.EntityFrameworkCore; using Microsoft.eShopWeb; using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.Infrastructure.Data; @@ -21,7 +23,19 @@ var builder = WebApplication.CreateBuilder(args); builder.Logging.AddConsole(); -Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); +// Configure SQL Server +var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); +builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); +builder.Services.AddDbContext(c => +{ + var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"]]; + c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); +}); +builder.Services.AddDbContext(options => +{ + var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"]]; + options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); +}); builder.Services.AddCookieSettings(); diff --git a/src/Web/Web.csproj b/src/Web/Web.csproj index 1d06eb0..6ec695d 100644 --- a/src/Web/Web.csproj +++ b/src/Web/Web.csproj @@ -17,6 +17,8 @@ + + From fc3f7b27469c1370cc9a14f99c3aa37c4eaf33a3 Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 31 Jan 2023 10:50:21 +0800 Subject: [PATCH 22/39] Remove invalid files --- scripts/setup-database.ps1 | 9 --------- scripts/setup-database.sh | 12 ------------ 2 files changed, 21 deletions(-) delete mode 100644 scripts/setup-database.ps1 delete mode 100644 scripts/setup-database.sh diff --git a/scripts/setup-database.ps1 b/scripts/setup-database.ps1 deleted file mode 100644 index 8969760..0000000 --- a/scripts/setup-database.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -# Default to current directory -$FOLDER_PATH = [string](Get-Location) - -# Before setting up your database, make sure of two things: -# 1. Ensure your connection strings in appsettings.json point to a local SQL Server instance. -# 2. Ensure the tool EF was already installed. -dotnet tool update --global dotnet-ef -dotnet ef database update -c catalogcontext -p $FOLDER_PATH/src/Infrastructure/Infrastructure.csproj -s $FOLDER_PATH/src/Web/Web.csproj -dotnet ef database update -c appidentitydbcontext -p $FOLDER_PATH/src/Infrastructure/Infrastructure.csproj -s $FOLDER_PATH/src/Web/Web.csproj \ No newline at end of file diff --git a/scripts/setup-database.sh b/scripts/setup-database.sh deleted file mode 100644 index abd4e60..0000000 --- a/scripts/setup-database.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# Default to current directory -FOLDER_PATH=$(pwd) - - -# Before setting up your database, make sure of two things: -# 1. Ensure your connection strings in appsettings.json point to a local SQL Server instance. -# 2. Ensure the tool EF was already installed. -dotnet tool update --global dotnet-ef -dotnet ef database update -c catalogcontext -p $FOLDER_PATH/src/Infrastructure/Infrastructure.csproj -s $FOLDER_PATH/src/Web/Web.csproj -dotnet ef database update -c appidentitydbcontext -p $FOLDER_PATH/src/Infrastructure/Infrastructure.csproj -s $FOLDER_PATH/src/Web/Web.csproj \ No newline at end of file From caa3ca2508a3025b4719aee97949ddabe40d8623 Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 31 Jan 2023 11:32:13 +0800 Subject: [PATCH 23/39] Add version in Packages.prop --- Directory.Packages.props | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1297b91..849fa1f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,6 +11,8 @@ + + From 33d8d5c50f8535921189b2374964ccb0cbbdcd3d Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 31 Jan 2023 11:47:10 +0800 Subject: [PATCH 24/39] Fix Azure.Identity version in Packages.prop --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 849fa1f..4573d60 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,7 +12,7 @@ - + From d27aeffeac159d8f48f859ec6026a0357d4630ac Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 31 Jan 2023 13:34:42 +0800 Subject: [PATCH 25/39] revert git version --- .devcontainer/Dockerfile | 3 +- .devcontainer/devcontainer.json | 23 ++++--- .github/workflows/dotnetcore.yml | 2 +- .github/workflows/richnav.yml | 2 +- .vscode/launch.json | 2 +- Directory.Packages.props | 68 +++++++++++++++++++ README.md | 6 +- eShopOnWeb.sln | 3 +- global.json | 2 +- src/ApplicationCore/ApplicationCore.csproj | 14 ++-- src/BlazorAdmin/BlazorAdmin.csproj | 25 +++---- src/BlazorAdmin/CustomAuthStateProvider.cs | 2 +- src/BlazorAdmin/Pages/Logout.razor | 2 +- src/BlazorAdmin/Shared/RedirectToLogin.razor | 11 +-- src/BlazorShared/BlazorShared.csproj | 7 +- src/Infrastructure/Infrastructure.csproj | 13 ++-- .../CatalogBrandListEndpoint.cs | 10 ++- .../CatalogItemGetByIdEndpoint.cs | 10 ++- .../CatalogItemListPagedEndpoint.cs | 15 ++-- .../CreateCatalogItemEndpoint.cs | 16 ++--- .../DeleteCatalogItemEndpoint.cs | 13 ++-- .../UpdateCatalogItemEndpoint.cs | 16 ++--- .../CatalogTypeListEndpoint.cs | 14 ++-- src/PublicApi/Dockerfile | 4 +- src/PublicApi/Program.cs | 4 +- src/PublicApi/Properties/launchSettings.json | 35 +++++++--- src/PublicApi/PublicApi.csproj | 37 +++++----- src/Web/.config/dotnet-tools.json | 2 +- .../Identity/Pages/Account/Logout.cshtml.cs | 6 +- src/Web/Configuration/ConfigureWebServices.cs | 2 - src/Web/Controllers/UserController.cs | 41 +++++++++-- src/Web/Dockerfile | 4 +- .../Features/MyOrders/GetMyOrdersHandler.cs | 6 +- .../OrderDetails/GetOrderDetailsHandler.cs | 5 +- src/Web/Program.cs | 2 +- src/Web/Web.csproj | 43 ++++++------ src/Web/libman.json | 12 ++-- tests/FunctionalTests/FunctionalTests.csproj | 18 ++--- .../IntegrationTests/IntegrationTests.csproj | 13 ++-- .../CatalogItemListPagedEndpoint.cs | 24 +++++++ .../PublicApiIntegrationTests.csproj | 16 +++-- tests/UnitTests/UnitTests.csproj | 21 ++---- 42 files changed, 338 insertions(+), 236 deletions(-) create mode 100644 Directory.Packages.props diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 54d859f..aa8d926 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,8 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 + # This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" # property in devcontainer.json to use it. On Linux, the container user's GID/UIDs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 046068a..1606d8e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -25,15 +25,22 @@ // Comment out to connect as root user. See https://aka.ms/vscode-remote/containers/non-root. // make sure this is the same as USERNAME above "remoteUser": "vscode", + "runArgs": [ + "-v", + "/var/run/docker.sock:/var/run/docker.sock" + ], + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.profiles.linux": { + "bash": { + "path": "bash", + "icon": "terminal-bash" + } + } + }, - // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash" - }, - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-azuretools.azure-dev", + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ "ms-dotnettools.csharp", "formulahendry.dotnet-test-explorer", "ms-vscode.vscode-node-azure-pack", diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 4fcc6d4..d5b7ee8 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -12,7 +12,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: '7.0.x' include-prerelease: true - name: Build with dotnet diff --git a/.github/workflows/richnav.yml b/.github/workflows/richnav.yml index c21c216..977ee7c 100644 --- a/.github/workflows/richnav.yml +++ b/.github/workflows/richnav.yml @@ -12,7 +12,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 3.1.x + dotnet-version: 7.0.x - name: Build with dotnet run: dotnet build ./eShopOnWeb.sln --configuration Release diff --git a/.vscode/launch.json b/.vscode/launch.json index d375cae..d6ed320 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/Web/bin/Debug/net5.0/Web.dll", + "program": "${workspaceFolder}/src/Web/bin/Debug/net7.0/Web.dll", "args": [], "cwd": "${workspaceFolder}/src/Web", "stopAtEntry": false, diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..4573d60 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,68 @@ + + + true + net7.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index d10d35b..9f8fa72 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The **eShopOnWeb** sample is related to the [eShopOnContainers](https://github.c The goal for this sample is to demonstrate some of the principles and patterns described in the [eBook](https://aka.ms/webappebook). It is not meant to be an eCommerce reference application, and as such it does not implement many features that would be obvious and/or essential to a real eCommerce application. > ### VERSIONS -> #### The `main` branch is currently running ASP.NET Core 6.0. +> #### The `main` branch is currently running ASP.NET Core 7.0. > #### Older versions are tagged. ## Topics (eBook TOC) @@ -47,7 +47,9 @@ The store's home page should look like this: ![eShopOnWeb home page screenshot](https://user-images.githubusercontent.com/782127/88414268-92d83a00-cdaa-11ea-9b4c-db67d95be039.png) -Most of the site's functionality works with just the web application running. However, the site's Admin page relies on Blazor WebAssembly running in the browser, and it must communicate with the server using the site's PublicApi web application. You'll need to also run this project. You can configure Visual Studio to start multiple projects, or just go to the PublicApi folder in a terminal window and run `dotnet run` from there. After that from the Web folder you should run `dotnet run --launch-profile Web`. Now you should be able to browse to `https://localhost:5001/`. Note that if you use this approach, you'll need to stop the application manually in order to build the solution (otherwise you'll get file locking errors). +Most of the site's functionality works with just the web application running. However, the site's Admin page relies on Blazor WebAssembly running in the browser, and it must communicate with the server using the site's PublicApi web application. You'll need to also run this project. You can configure Visual Studio to start multiple projects, or just go to the PublicApi folder in a terminal window and run `dotnet run` from there. After that from the Web folder you should run `dotnet run --launch-profile Web`. Now you should be able to browse to `https://localhost:5001/`. The admin part in Blazor is accessible to `https://localhost:5001/admin` + +Note that if you use this approach, you'll need to stop the application manually in order to build the solution (otherwise you'll get file locking errors). After cloning or downloading the sample you must setup your database. To use the sample with a persistent database, you will need to run its Entity Framework Core migrations before you will be able to run the app. diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index 2adaaf8..e878f9a 100755 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -24,6 +24,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0BD72BEA-EF42-4B72-8B69-12A39EC76FBA}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + Directory.Packages.props = Directory.Packages.props docker-compose.override.yml = docker-compose.override.yml docker-compose.yml = docker-compose.yml .github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml @@ -38,7 +39,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorAdmin", "src\BlazorAd EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorShared", "src\BlazorShared\BlazorShared.csproj", "{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublicApiIntegrationTests", "tests\PublicApiIntegrationTests\PublicApiIntegrationTests.csproj", "{D53EF010-8F8C-4337-A059-456E19D8AE63}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PublicApiIntegrationTests", "tests\PublicApiIntegrationTests\PublicApiIntegrationTests.csproj", "{D53EF010-8F8C-4337-A059-456E19D8AE63}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/global.json b/global.json index 957199c..8ec4b2b 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.x", + "version": "7.0.x", "rollForward": "latestFeature" } } diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index 2d2c5cd..a0a5bbe 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,18 +1,16 @@  - - net6.0 + Microsoft.eShopWeb.ApplicationCore enable - - - - - - + + + + + diff --git a/src/BlazorAdmin/BlazorAdmin.csproj b/src/BlazorAdmin/BlazorAdmin.csproj index 5eb9251..393b330 100644 --- a/src/BlazorAdmin/BlazorAdmin.csproj +++ b/src/BlazorAdmin/BlazorAdmin.csproj @@ -1,19 +1,14 @@ - - - - net6.0 - - + - - - - - - - - - + + + + + + + + + diff --git a/src/BlazorAdmin/CustomAuthStateProvider.cs b/src/BlazorAdmin/CustomAuthStateProvider.cs index b12d232..30cf42c 100644 --- a/src/BlazorAdmin/CustomAuthStateProvider.cs +++ b/src/BlazorAdmin/CustomAuthStateProvider.cs @@ -63,7 +63,7 @@ public class CustomAuthStateProvider : AuthenticationStateProvider if (user == null || !user.IsAuthenticated) { - return null; + return new ClaimsPrincipal(new ClaimsIdentity()); } var identity = new ClaimsIdentity( diff --git a/src/BlazorAdmin/Pages/Logout.razor b/src/BlazorAdmin/Pages/Logout.razor index ddcd25a..ada679c 100644 --- a/src/BlazorAdmin/Pages/Logout.razor +++ b/src/BlazorAdmin/Pages/Logout.razor @@ -7,7 +7,7 @@ protected override async Task OnInitializedAsync() { - await HttpClient.PostAsync("Identity/Account/Logout", null); + await HttpClient.PostAsync("User/Logout", null); await new Route(JSRuntime).RouteOutside("/Identity/Account/Login"); } diff --git a/src/BlazorAdmin/Shared/RedirectToLogin.razor b/src/BlazorAdmin/Shared/RedirectToLogin.razor index 810a66e..de9c49d 100644 --- a/src/BlazorAdmin/Shared/RedirectToLogin.razor +++ b/src/BlazorAdmin/Shared/RedirectToLogin.razor @@ -1,9 +1,12 @@ -@inject NavigationManager Navigation +@using System.Web; + +@inject NavigationManager Navigation +@inject IJSRuntime JsRuntime @code { protected override void OnInitialized() - { - Navigation.NavigateTo($"Identity/Account/Login?returnUrl=" + - $"/{Uri.EscapeDataString(Navigation.ToBaseRelativePath(Navigation.Uri))}"); + { + var returnUrl = HttpUtility.UrlEncode($"/{Uri.EscapeDataString(Navigation.ToBaseRelativePath(Navigation.Uri))}"); + JsRuntime.InvokeVoidAsync("location.replace", $"Identity/Account/Login?returnUrl={returnUrl}"); } } \ No newline at end of file diff --git a/src/BlazorShared/BlazorShared.csproj b/src/BlazorShared/BlazorShared.csproj index c0aa99c..8cd1e29 100644 --- a/src/BlazorShared/BlazorShared.csproj +++ b/src/BlazorShared/BlazorShared.csproj @@ -1,14 +1,13 @@  - - net6.0 + BlazorShared BlazorShared - - + + diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index a0be8bb..026a8ca 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -1,17 +1,16 @@  - - net6.0 + Microsoft.eShopWeb.Infrastructure enable - - - - - + + + + + diff --git a/src/PublicApi/CatalogBrandEndpoints/CatalogBrandListEndpoint.cs b/src/PublicApi/CatalogBrandEndpoints/CatalogBrandListEndpoint.cs index dce823e..32b3d0b 100644 --- a/src/PublicApi/CatalogBrandEndpoints/CatalogBrandListEndpoint.cs +++ b/src/PublicApi/CatalogBrandEndpoints/CatalogBrandListEndpoint.cs @@ -13,9 +13,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints; /// /// List Catalog Brands /// -public class CatalogBrandListEndpoint : IEndpoint +public class CatalogBrandListEndpoint : IEndpoint> { - private IRepository _catalogBrandRepository; private readonly IMapper _mapper; public CatalogBrandListEndpoint(IMapper mapper) @@ -28,18 +27,17 @@ public class CatalogBrandListEndpoint : IEndpoint app.MapGet("api/catalog-brands", async (IRepository catalogBrandRepository) => { - _catalogBrandRepository = catalogBrandRepository; - return await HandleAsync(); + return await HandleAsync(catalogBrandRepository); }) .Produces() .WithTags("CatalogBrandEndpoints"); } - public async Task HandleAsync() + public async Task HandleAsync(IRepository catalogBrandRepository) { var response = new ListCatalogBrandsResponse(); - var items = await _catalogBrandRepository.ListAsync(); + var items = await catalogBrandRepository.ListAsync(); response.CatalogBrands.AddRange(items.Select(_mapper.Map)); diff --git a/src/PublicApi/CatalogItemEndpoints/CatalogItemGetByIdEndpoint.cs b/src/PublicApi/CatalogItemEndpoints/CatalogItemGetByIdEndpoint.cs index a1f6011..9f15c72 100644 --- a/src/PublicApi/CatalogItemEndpoints/CatalogItemGetByIdEndpoint.cs +++ b/src/PublicApi/CatalogItemEndpoints/CatalogItemGetByIdEndpoint.cs @@ -11,9 +11,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; /// /// Get a Catalog Item by Id /// -public class CatalogItemGetByIdEndpoint : IEndpoint +public class CatalogItemGetByIdEndpoint : IEndpoint> { - private IRepository _itemRepository; private readonly IUriComposer _uriComposer; public CatalogItemGetByIdEndpoint(IUriComposer uriComposer) @@ -26,18 +25,17 @@ public class CatalogItemGetByIdEndpoint : IEndpoint itemRepository) => { - _itemRepository = itemRepository; - return await HandleAsync(new GetByIdCatalogItemRequest(catalogItemId)); + return await HandleAsync(new GetByIdCatalogItemRequest(catalogItemId), itemRepository); }) .Produces() .WithTags("CatalogItemEndpoints"); } - public async Task HandleAsync(GetByIdCatalogItemRequest request) + public async Task HandleAsync(GetByIdCatalogItemRequest request, IRepository itemRepository) { var response = new GetByIdCatalogItemResponse(request.CorrelationId()); - var item = await _itemRepository.GetByIdAsync(request.CatalogItemId); + var item = await itemRepository.GetByIdAsync(request.CatalogItemId); if (item is null) return Results.NotFound(); diff --git a/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs b/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs index 308d310..920fe4f 100644 --- a/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs +++ b/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs @@ -15,9 +15,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; /// /// List Catalog Items (paged) /// -public class CatalogItemListPagedEndpoint : IEndpoint +public class CatalogItemListPagedEndpoint : IEndpoint> { - private IRepository _itemRepository; private readonly IUriComposer _uriComposer; private readonly IMapper _mapper; @@ -32,19 +31,19 @@ public class CatalogItemListPagedEndpoint : IEndpoint itemRepository) => { - _itemRepository = itemRepository; - return await HandleAsync(new ListPagedCatalogItemRequest(pageSize, pageIndex, catalogBrandId, catalogTypeId)); - }) + return await HandleAsync(new ListPagedCatalogItemRequest(pageSize, pageIndex, catalogBrandId, catalogTypeId), itemRepository); + }) .Produces() .WithTags("CatalogItemEndpoints"); } - public async Task HandleAsync(ListPagedCatalogItemRequest request) + public async Task HandleAsync(ListPagedCatalogItemRequest request, IRepository itemRepository) { + await Task.Delay(1000); var response = new ListPagedCatalogItemResponse(request.CorrelationId()); var filterSpec = new CatalogFilterSpecification(request.CatalogBrandId, request.CatalogTypeId); - int totalItems = await _itemRepository.CountAsync(filterSpec); + int totalItems = await itemRepository.CountAsync(filterSpec); var pagedSpec = new CatalogFilterPaginatedSpecification( skip: request.PageIndex.Value * request.PageSize.Value, @@ -52,7 +51,7 @@ public class CatalogItemListPagedEndpoint : IEndpoint)); foreach (CatalogItemDto item in response.CatalogItems) diff --git a/src/PublicApi/CatalogItemEndpoints/CreateCatalogItemEndpoint.cs b/src/PublicApi/CatalogItemEndpoints/CreateCatalogItemEndpoint.cs index 25527f9..c15346f 100644 --- a/src/PublicApi/CatalogItemEndpoints/CreateCatalogItemEndpoint.cs +++ b/src/PublicApi/CatalogItemEndpoints/CreateCatalogItemEndpoint.cs @@ -15,9 +15,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; /// /// Creates a new Catalog Item /// -public class CreateCatalogItemEndpoint : IEndpoint +public class CreateCatalogItemEndpoint : IEndpoint> { - private IRepository _itemRepository; private readonly IUriComposer _uriComposer; public CreateCatalogItemEndpoint(IUriComposer uriComposer) @@ -31,26 +30,25 @@ public class CreateCatalogItemEndpoint : IEndpoint itemRepository) => { - _itemRepository = itemRepository; - return await HandleAsync(request); + return await HandleAsync(request, itemRepository); }) .Produces() .WithTags("CatalogItemEndpoints"); } - public async Task HandleAsync(CreateCatalogItemRequest request) + public async Task HandleAsync(CreateCatalogItemRequest request, IRepository itemRepository) { var response = new CreateCatalogItemResponse(request.CorrelationId()); var catalogItemNameSpecification = new CatalogItemNameSpecification(request.Name); - var existingCataloogItem = await _itemRepository.CountAsync(catalogItemNameSpecification); + var existingCataloogItem = await itemRepository.CountAsync(catalogItemNameSpecification); if (existingCataloogItem > 0) { throw new DuplicateException($"A catalogItem with name {request.Name} already exists"); } var newItem = new CatalogItem(request.CatalogTypeId, request.CatalogBrandId, request.Description, request.Name, request.Price, request.PictureUri); - newItem = await _itemRepository.AddAsync(newItem); + newItem = await itemRepository.AddAsync(newItem); if (newItem.Id != 0) { @@ -59,7 +57,7 @@ public class CreateCatalogItemEndpoint : IEndpoint /// Deletes a Catalog Item /// -public class DeleteCatalogItemEndpoint : IEndpoint +public class DeleteCatalogItemEndpoint : IEndpoint> { - private IRepository _itemRepository; - public void AddRoute(IEndpointRouteBuilder app) { app.MapDelete("api/catalog-items/{catalogItemId}", [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async (int catalogItemId, IRepository itemRepository) => { - _itemRepository = itemRepository; - return await HandleAsync(new DeleteCatalogItemRequest(catalogItemId)); + return await HandleAsync(new DeleteCatalogItemRequest(catalogItemId), itemRepository); }) .Produces() .WithTags("CatalogItemEndpoints"); } - public async Task HandleAsync(DeleteCatalogItemRequest request) + public async Task HandleAsync(DeleteCatalogItemRequest request, IRepository itemRepository) { var response = new DeleteCatalogItemResponse(request.CorrelationId()); - var itemToDelete = await _itemRepository.GetByIdAsync(request.CatalogItemId); + var itemToDelete = await itemRepository.GetByIdAsync(request.CatalogItemId); if (itemToDelete is null) return Results.NotFound(); - await _itemRepository.DeleteAsync(itemToDelete); + await itemRepository.DeleteAsync(itemToDelete); return Results.Ok(response); } diff --git a/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs b/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs index 2ce0e6c..b923322 100644 --- a/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs +++ b/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs @@ -13,9 +13,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; /// /// Updates a Catalog Item /// -public class UpdateCatalogItemEndpoint : IEndpoint -{ - private IRepository _itemRepository; +public class UpdateCatalogItemEndpoint : IEndpoint> +{ private readonly IUriComposer _uriComposer; public UpdateCatalogItemEndpoint(IUriComposer uriComposer) @@ -29,25 +28,24 @@ public class UpdateCatalogItemEndpoint : IEndpoint itemRepository) => { - _itemRepository = itemRepository; - return await HandleAsync(request); + return await HandleAsync(request, itemRepository); }) .Produces() .WithTags("CatalogItemEndpoints"); } - public async Task HandleAsync(UpdateCatalogItemRequest request) + public async Task HandleAsync(UpdateCatalogItemRequest request, IRepository itemRepository) { var response = new UpdateCatalogItemResponse(request.CorrelationId()); - var existingItem = await _itemRepository.GetByIdAsync(request.Id); - + var existingItem = await itemRepository.GetByIdAsync(request.Id); + CatalogItem.CatalogItemDetails details = new(request.Name, request.Description, request.Price); existingItem.UpdateDetails(details); existingItem.UpdateBrand(request.CatalogBrandId); existingItem.UpdateType(request.CatalogTypeId); - await _itemRepository.UpdateAsync(existingItem); + await itemRepository.UpdateAsync(existingItem); var dto = new CatalogItemDto { diff --git a/src/PublicApi/CatalogTypeEndpoints/CatalogTypeListEndpoint.cs b/src/PublicApi/CatalogTypeEndpoints/CatalogTypeListEndpoint.cs index 87aa035..3e36735 100644 --- a/src/PublicApi/CatalogTypeEndpoints/CatalogTypeListEndpoint.cs +++ b/src/PublicApi/CatalogTypeEndpoints/CatalogTypeListEndpoint.cs @@ -13,33 +13,31 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints; /// /// List Catalog Types /// -public class CatalogTypeListEndpoint : IEndpoint +public class CatalogTypeListEndpoint : IEndpoint> { - private IRepository _catalogTypeRepository; private readonly IMapper _mapper; public CatalogTypeListEndpoint(IMapper mapper) - { + { _mapper = mapper; } public void AddRoute(IEndpointRouteBuilder app) { - app.MapGet("api/catalog-types", + app.MapGet("api/catalog-types", async (IRepository catalogTypeRepository) => { - _catalogTypeRepository = catalogTypeRepository; - return await HandleAsync(); + return await HandleAsync(catalogTypeRepository); }) .Produces() .WithTags("CatalogTypeEndpoints"); } - public async Task HandleAsync() + public async Task HandleAsync(IRepository catalogTypeRepository) { var response = new ListCatalogTypesResponse(); - var items = await _catalogTypeRepository.ListAsync(); + var items = await catalogTypeRepository.ListAsync(); response.CatalogTypes.AddRange(items.Select(_mapper.Map)); diff --git a/src/PublicApi/Dockerfile b/src/PublicApi/Dockerfile index f768db5..885a155 100644 --- a/src/PublicApi/Dockerfile +++ b/src/PublicApi/Dockerfile @@ -1,11 +1,11 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /app COPY . . #COPY ["src/PublicApi/PublicApi.csproj", "./PublicApi/"] diff --git a/src/PublicApi/Program.cs b/src/PublicApi/Program.cs index 7356a62..2a16551 100644 --- a/src/PublicApi/Program.cs +++ b/src/PublicApi/Program.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Text; using BlazorShared; using BlazorShared.Models; -using MediatR; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; @@ -83,9 +82,8 @@ builder.Services.AddCors(options => }); builder.Services.AddControllers(); - -builder.Services.AddMediatR(typeof(CatalogItem).Assembly); builder.Services.AddAutoMapper(typeof(MappingProfile).Assembly); +builder.Configuration.AddEnvironmentVariables(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => diff --git a/src/PublicApi/Properties/launchSettings.json b/src/PublicApi/Properties/launchSettings.json index fbed524..c44d516 100644 --- a/src/PublicApi/Properties/launchSettings.json +++ b/src/PublicApi/Properties/launchSettings.json @@ -1,13 +1,4 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:52023", - "sslPort": 44339 - } - }, - "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "IIS Express": { "commandName": "IISExpress", @@ -25,6 +16,32 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "https://localhost:5099;http://localhost:5098" + }, + "WSL": { + "commandName": "WSL2", + "launchBrowser": true, + "launchUrl": "https://localhost:5099/swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "https://localhost:5099;http://localhost:5098" + }, + "distributionName": "" + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:52023", + "sslPort": 44339 } } } \ No newline at end of file diff --git a/src/PublicApi/PublicApi.csproj b/src/PublicApi/PublicApi.csproj index 4677879..965ea6d 100644 --- a/src/PublicApi/PublicApi.csproj +++ b/src/PublicApi/PublicApi.csproj @@ -1,7 +1,6 @@  - - net6.0 + Microsoft.eShopWeb.PublicApi 5b662463-1efd-4bae-bde4-befe0be3e8ff Linux @@ -10,28 +9,26 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + diff --git a/src/Web/.config/dotnet-tools.json b/src/Web/.config/dotnet-tools.json index 5060fd6..f331fe5 100644 --- a/src/Web/.config/dotnet-tools.json +++ b/src/Web/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "6.0.4", + "version": "7.0.2", "commands": [ "dotnet-ef" ] diff --git a/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs index 8af0afb..80bb467 100644 --- a/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs +++ b/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; +using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Identity; @@ -10,7 +7,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.eShopWeb.Infrastructure.Identity; using Microsoft.eShopWeb.Web.Configuration; using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Logging; namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account; diff --git a/src/Web/Configuration/ConfigureWebServices.cs b/src/Web/Configuration/ConfigureWebServices.cs index 478b89d..f89a99a 100644 --- a/src/Web/Configuration/ConfigureWebServices.cs +++ b/src/Web/Configuration/ConfigureWebServices.cs @@ -1,8 +1,6 @@ using MediatR; using Microsoft.eShopWeb.Web.Interfaces; using Microsoft.eShopWeb.Web.Services; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.eShopWeb.Web.Configuration; diff --git a/src/Web/Controllers/UserController.cs b/src/Web/Controllers/UserController.cs index 05d55c2..79968c1 100644 --- a/src/Web/Controllers/UserController.cs +++ b/src/Web/Controllers/UserController.cs @@ -1,11 +1,14 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; +using System.Security.Claims; using BlazorShared.Authorization; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.eShopWeb.Infrastructure.Identity; +using Microsoft.eShopWeb.Web.Configuration; +using Microsoft.Extensions.Caching.Memory; namespace Microsoft.eShopWeb.Web.Controllers; @@ -14,10 +17,19 @@ namespace Microsoft.eShopWeb.Web.Controllers; public class UserController : ControllerBase { private readonly ITokenClaimsService _tokenClaimsService; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + private readonly IMemoryCache _cache; - public UserController(ITokenClaimsService tokenClaimsService) + public UserController(ITokenClaimsService tokenClaimsService, + SignInManager signInManager, + ILogger logger, + IMemoryCache cache) { _tokenClaimsService = tokenClaimsService; + _signInManager = signInManager; + _logger = logger; + _cache = cache; } [HttpGet] @@ -26,6 +38,25 @@ public class UserController : ControllerBase public async Task GetCurrentUser() => Ok(await CreateUserInfo(User)); + [Route("Logout")] + [HttpPost] + [Authorize] + [AllowAnonymous] + public async Task Logout() + { + await _signInManager.SignOutAsync(); + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + var userId = _signInManager.Context.User.Claims.First(c => c.Type == ClaimTypes.Name); + var identityKey = _signInManager.Context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName]; + _cache.Set($"{userId.Value}:{identityKey}", identityKey, new MemoryCacheEntryOptions + { + AbsoluteExpiration = DateTime.Now.AddMinutes(ConfigureCookieSettings.ValidityMinutesPeriod) + }); + + _logger.LogInformation("User logged out."); + return Ok(); + } + private async Task CreateUserInfo(ClaimsPrincipal claimsPrincipal) { if (claimsPrincipal.Identity == null || claimsPrincipal.Identity.Name == null || !claimsPrincipal.Identity.IsAuthenticated) diff --git a/src/Web/Dockerfile b/src/Web/Dockerfile index 9062398..9a5e23d 100644 --- a/src/Web/Dockerfile +++ b/src/Web/Dockerfile @@ -7,7 +7,7 @@ # # RUN COMMAND # docker run --name eshopweb --rm -it -p 5106:5106 web -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /app COPY *.sln . @@ -17,7 +17,7 @@ RUN dotnet restore RUN dotnet publish -c Release -o out -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS runtime WORKDIR /app COPY --from=build /app/src/Web/out ./ diff --git a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs index 6cd64af..125f0a9 100644 --- a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs +++ b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediatR; +using MediatR; using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.ApplicationCore.Specifications; diff --git a/src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs b/src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs index dab5050..be7b8dc 100644 --- a/src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs +++ b/src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs @@ -1,7 +1,4 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediatR; +using MediatR; using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.ApplicationCore.Specifications; diff --git a/src/Web/Program.cs b/src/Web/Program.cs index a998b28..593232c 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -53,7 +53,7 @@ builder.Services.AddIdentity() .AddDefaultTokenProviders(); builder.Services.AddScoped(); - +builder.Configuration.AddEnvironmentVariables(); builder.Services.AddCoreServices(builder.Configuration); builder.Services.AddWebServices(builder.Configuration); diff --git a/src/Web/Web.csproj b/src/Web/Web.csproj index 6ec695d..35c6f0a 100644 --- a/src/Web/Web.csproj +++ b/src/Web/Web.csproj @@ -1,7 +1,6 @@  - - net6.0 + enable enable Microsoft.eShopWeb.Web @@ -14,28 +13,28 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/Web/libman.json b/src/Web/libman.json index ac8c0f9..d841fcf 100644 --- a/src/Web/libman.json +++ b/src/Web/libman.json @@ -3,11 +3,11 @@ "defaultProvider": "cdnjs", "libraries": [ { - "library": "jquery@3.3.1", + "library": "jquery@3.6.3", "destination": "wwwroot/lib/jquery/" }, { - "library": "twitter-bootstrap@3.3.7", + "library": "twitter-bootstrap@5.2.3", "files": [ "css/bootstrap.css", "css/bootstrap.css.map", @@ -19,11 +19,11 @@ "destination": "wwwroot/lib/bootstrap/dist/" }, { - "library": "jquery-validation-unobtrusive@3.2.10", + "library": "jquery-validation-unobtrusive@4.0.0", "destination": "wwwroot/lib/jquery-validation-unobtrusive/" }, { - "library": "jquery-validate@1.17.0", + "library": "jquery-validate@1.19.5", "destination": "wwwroot/lib/jquery-validate/", "files": [ "jquery.validate.min.js", @@ -35,7 +35,7 @@ "destination": "wwwroot/lib/toastr/" }, { - "library": "aspnet-signalr@1.0.3", + "library": "aspnet-signalr@1.0.27", "files": [ "signalr.js", "signalr.min.js" @@ -43,4 +43,4 @@ "destination": "wwwroot/lib/@aspnet/signalr/dist/browser/" } ] -} \ No newline at end of file +} diff --git a/tests/FunctionalTests/FunctionalTests.csproj b/tests/FunctionalTests/FunctionalTests.csproj index ba3f67d..f484cf9 100644 --- a/tests/FunctionalTests/FunctionalTests.csproj +++ b/tests/FunctionalTests/FunctionalTests.csproj @@ -1,7 +1,6 @@  - - net6.0 + Microsoft.eShopWeb.FunctionalTests false enable @@ -15,15 +14,12 @@ - - - - - all - runtime; build; native; contentfiles; analyzers - - - + + + + + + diff --git a/tests/IntegrationTests/IntegrationTests.csproj b/tests/IntegrationTests/IntegrationTests.csproj index 7473d83..5876dc3 100644 --- a/tests/IntegrationTests/IntegrationTests.csproj +++ b/tests/IntegrationTests/IntegrationTests.csproj @@ -1,17 +1,16 @@  - - net6.0 + Microsoft.eShopWeb.IntegrationTests false - - - - - + + + + + all runtime; build; native; contentfiles; analyzers diff --git a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs index 1040463..5eb3036 100644 --- a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs +++ b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs @@ -2,7 +2,10 @@ using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; using Microsoft.eShopWeb.Web.ViewModels; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; using System.Linq; +using System.Net.Http; +using System.Net; using System.Threading.Tasks; namespace PublicApiIntegrationTests.CatalogItemEndpoints @@ -45,5 +48,26 @@ namespace PublicApiIntegrationTests.CatalogItemEndpoints Assert.AreEqual(totalExpected, model2.CatalogItems.Count()); } + + [DataTestMethod] + [DataRow("catalog-items")] + [DataRow("catalog-brands")] + [DataRow("catalog-types")] + [DataRow("catalog-items/1")] + public async Task SuccessFullMutipleParallelCall(string endpointName) + { + var client = ProgramTest.NewClient; + var tasks = new List>(); + + for (int i = 0; i < 100; i++) + { + var task = client.GetAsync($"/api/{endpointName}"); + tasks.Add(task); + } + await Task.WhenAll(tasks.ToList()); + var totalKO = tasks.Count(t => t.Result.StatusCode != HttpStatusCode.OK); + + Assert.AreEqual(0, totalKO); + } } } diff --git a/tests/PublicApiIntegrationTests/PublicApiIntegrationTests.csproj b/tests/PublicApiIntegrationTests/PublicApiIntegrationTests.csproj index d560cac..467bc1f 100644 --- a/tests/PublicApiIntegrationTests/PublicApiIntegrationTests.csproj +++ b/tests/PublicApiIntegrationTests/PublicApiIntegrationTests.csproj @@ -1,7 +1,6 @@ - - net6.0 + enable false @@ -20,11 +19,14 @@ - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/UnitTests/UnitTests.csproj b/tests/UnitTests/UnitTests.csproj index 7381753..f8bbe01 100644 --- a/tests/UnitTests/UnitTests.csproj +++ b/tests/UnitTests/UnitTests.csproj @@ -1,7 +1,6 @@  - - net6.0 + enable Microsoft.eShopWeb.UnitTests false @@ -10,18 +9,12 @@ - - - - - - all - runtime; build; native; contentfiles; analyzers - - - all - runtime; build; native; contentfiles; analyzers - + + + + + + From 3bb6bf89b891a0152e5c4c2c44438f2907dba9de Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 31 Jan 2023 15:33:56 +0800 Subject: [PATCH 26/39] fix dotnet version --- infra/main.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index b8c5ebb..f1187e7 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -54,7 +54,7 @@ module web './core/host/appservice.bicep' = { appServicePlanId: appServicePlan.outputs.id keyVaultName: keyVault.outputs.name runtimeName: 'dotnetcore' - runtimeVersion: '6.0' + runtimeVersion: '7.0' tags: union(tags, { 'azd-service-name': 'web' }) appSettings: { AZURE_SQL_CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING' From 2a67d21396e725210287ed642e2cb64193bbdc74 Mon Sep 17 00:00:00 2001 From: zedy Date: Thu, 2 Feb 2023 18:46:00 +0800 Subject: [PATCH 27/39] update readme --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++- src/Web/Program.cs | 5 ++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f8fa72..7f9873d 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,46 @@ The goal for this sample is to demonstrate some of the principles and patterns d - Development Process for Azure-Hosted ASP.NET Core Apps - Azure Hosting Recommendations for ASP.NET Core Web Apps -## Running the sample +## Running the sample using Azd template The store's home page should look like this: ![eShopOnWeb home page screenshot](https://user-images.githubusercontent.com/782127/88414268-92d83a00-cdaa-11ea-9b4c-db67d95be039.png) +The Azure Developer CLI (`azd`) is a developer-centric command-line interface (CLI) tool for creating Azure applications. + +You need to install it before running and deploying with Azure Developer CLI. + +### Windows + +```powershell +powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression" +``` + +### Linux/MacOS + +``` +curl -fsSL https://aka.ms/install-azd.sh | bash +``` + +After logging in with the following command, you will be able to use the azd cli to quickly provision and deploy the application. + +``` +azd login +``` + +Then, just use the `azd up` command to complete all operations of clone, provision and deployment. +``` +azd up -t dotnet-architecture/eShopOnWeb -b main +``` +According to the prompt, enter an `env name`, and select `subscription` and `location`, these are the necessary parameters when you create resources. Wait a moment for the resource deployment to complete, click the web endpoint and you will see the home page. + +**Notes:** +Considering security, we store its related data (id, password) in the key vault when we create the database, and obtain it from the key vault when we use it. This is different from directly deploying applications locally. + +You can also run the sample directly locally, some adjustments are required when configuring SQL Server (See below). + +## Running the sample locally Most of the site's functionality works with just the web application running. However, the site's Admin page relies on Blazor WebAssembly running in the browser, and it must communicate with the server using the site's PublicApi web application. You'll need to also run this project. You can configure Visual Studio to start multiple projects, or just go to the PublicApi folder in a terminal window and run `dotnet run` from there. After that from the Web folder you should run `dotnet run --launch-profile Web`. Now you should be able to browse to `https://localhost:5001/`. The admin part in Blazor is accessible to `https://localhost:5001/admin` Note that if you use this approach, you'll need to stop the application manually in order to build the solution (otherwise you'll get file locking errors). @@ -58,6 +92,25 @@ You can also run the samples in Docker (see below). ### Configuring the sample to use SQL Server +1. In the `src/Web/Program.cs` file, switch to the `Locally` configuration of SQL Server. + ``` csharp + // Configure SQL Server (Locally) + // Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); + + // Configure SQL Server (Azd template) + var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); + builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); + builder.Services.AddDbContext(c => + { + var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"]]; + c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); + }); + builder.Services.AddDbContext(options => + { + var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"]]; + options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); + }); + ``` 1. By default, the project uses a real database. If you want an in memory database, you can add in `appsettings.json` ```json diff --git a/src/Web/Program.cs b/src/Web/Program.cs index 593232c..9b1589c 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -23,7 +23,10 @@ var builder = WebApplication.CreateBuilder(args); builder.Logging.AddConsole(); -// Configure SQL Server +// Configure SQL Server (Locally) +// Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); + +// Configure SQL Server (Azd template) var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); builder.Services.AddDbContext(c => From cdc079a20b3483f4a296f635cb0088935caed1ce Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 7 Feb 2023 13:47:55 +0800 Subject: [PATCH 28/39] Add the AZD_TEMPLATE parameter in the configuration. When the value is enable, azd template logic will be used when configuring SQL Server, otherwise locally logic will be used. --- README.md | 21 +-------------------- infra/main.bicep | 1 + src/Web/Program.cs | 43 +++++++++++++++++++++++++++++++------------ 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 7f9873d..847acb4 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ According to the prompt, enter an `env name`, and select `subscription` and `loc **Notes:** Considering security, we store its related data (id, password) in the key vault when we create the database, and obtain it from the key vault when we use it. This is different from directly deploying applications locally. -You can also run the sample directly locally, some adjustments are required when configuring SQL Server (See below). +You can also run the sample directly locally. (See below). ## Running the sample locally Most of the site's functionality works with just the web application running. However, the site's Admin page relies on Blazor WebAssembly running in the browser, and it must communicate with the server using the site's PublicApi web application. You'll need to also run this project. You can configure Visual Studio to start multiple projects, or just go to the PublicApi folder in a terminal window and run `dotnet run` from there. After that from the Web folder you should run `dotnet run --launch-profile Web`. Now you should be able to browse to `https://localhost:5001/`. The admin part in Blazor is accessible to `https://localhost:5001/admin` @@ -92,25 +92,6 @@ You can also run the samples in Docker (see below). ### Configuring the sample to use SQL Server -1. In the `src/Web/Program.cs` file, switch to the `Locally` configuration of SQL Server. - ``` csharp - // Configure SQL Server (Locally) - // Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); - - // Configure SQL Server (Azd template) - var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); - builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); - builder.Services.AddDbContext(c => - { - var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"]]; - c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); - }); - builder.Services.AddDbContext(options => - { - var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"]]; - options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); - }); - ``` 1. By default, the project uses a real database. If you want an in memory database, you can add in `appsettings.json` ```json diff --git a/infra/main.bicep b/infra/main.bicep index f1187e7..096c853 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -60,6 +60,7 @@ module web './core/host/appservice.bicep' = { AZURE_SQL_CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING' AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY: 'AZURE-SQL-IDENTITY-CONNECTION-STRING' AZURE_KEY_VAULT_ENDPOINT: keyVault.outputs.endpoint + AZD_TEMPLATE: 'enable' } } } diff --git a/src/Web/Program.cs b/src/Web/Program.cs index 9b1589c..458ee8d 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -23,22 +23,41 @@ var builder = WebApplication.CreateBuilder(args); builder.Logging.AddConsole(); +var azdTemplate= builder.Configuration["AZD_TEMPLATE"]; + +if (azdTemplate == "enable"){ + var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); + builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); + builder.Services.AddDbContext(c => + { + var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"]]; + c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); + }); + builder.Services.AddDbContext(options => + { + var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"]]; + options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); + }); +} +else{ + Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); +} // Configure SQL Server (Locally) // Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); // Configure SQL Server (Azd template) -var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); -builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); -builder.Services.AddDbContext(c => -{ - var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"]]; - c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); -}); -builder.Services.AddDbContext(options => -{ - var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"]]; - options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); -}); +// var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); +// builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); +// builder.Services.AddDbContext(c => +// { +// var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"]]; +// c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); +// }); +// builder.Services.AddDbContext(options => +// { +// var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"]]; +// options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); +// }); builder.Services.AddCookieSettings(); From 69b2dc6d25976fddd3488e694b16ca87cc5b4fa4 Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 7 Feb 2023 14:35:49 +0800 Subject: [PATCH 29/39] Modify some comments and fix format --- README.md | 2 +- src/Web/Program.cs | 18 ++---------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 847acb4..674e728 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ According to the prompt, enter an `env name`, and select `subscription` and `loc **Notes:** Considering security, we store its related data (id, password) in the key vault when we create the database, and obtain it from the key vault when we use it. This is different from directly deploying applications locally. -You can also run the sample directly locally. (See below). +You can also run the sample directly locally (See below). ## Running the sample locally Most of the site's functionality works with just the web application running. However, the site's Admin page relies on Blazor WebAssembly running in the browser, and it must communicate with the server using the site's PublicApi web application. You'll need to also run this project. You can configure Visual Studio to start multiple projects, or just go to the PublicApi folder in a terminal window and run `dotnet run` from there. After that from the Web folder you should run `dotnet run --launch-profile Web`. Now you should be able to browse to `https://localhost:5001/`. The admin part in Blazor is accessible to `https://localhost:5001/admin` diff --git a/src/Web/Program.cs b/src/Web/Program.cs index 458ee8d..67d3e44 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -26,6 +26,7 @@ builder.Logging.AddConsole(); var azdTemplate= builder.Configuration["AZD_TEMPLATE"]; if (azdTemplate == "enable"){ + // Configure SQL Server (Azd template) var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); builder.Services.AddDbContext(c => @@ -40,24 +41,9 @@ if (azdTemplate == "enable"){ }); } else{ + // Configure SQL Server (Locally) Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); } -// Configure SQL Server (Locally) -// Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); - -// Configure SQL Server (Azd template) -// var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); -// builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); -// builder.Services.AddDbContext(c => -// { -// var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"]]; -// c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); -// }); -// builder.Services.AddDbContext(options => -// { -// var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"]]; -// options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); -// }); builder.Services.AddCookieSettings(); From 7e99523594068b688d5fd8e431a36fbefc9a0f18 Mon Sep 17 00:00:00 2001 From: zedy Date: Wed, 8 Feb 2023 15:33:28 +0800 Subject: [PATCH 30/39] fix azd up command --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 674e728..272fb32 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ azd login Then, just use the `azd up` command to complete all operations of clone, provision and deployment. ``` -azd up -t dotnet-architecture/eShopOnWeb -b main +azd up -t dotnet-architecture/eShopOnWeb ``` According to the prompt, enter an `env name`, and select `subscription` and `location`, these are the necessary parameters when you create resources. Wait a moment for the resource deployment to complete, click the web endpoint and you will see the home page. From 3e0ca9869d8154facf2dd48724557656de05c8fc Mon Sep 17 00:00:00 2001 From: zedy Date: Fri, 17 Feb 2023 11:10:10 +0800 Subject: [PATCH 31/39] update variable --- src/Web/Program.cs | 14 +++++++------- src/Web/appsettings.json | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Web/Program.cs b/src/Web/Program.cs index 67d3e44..a981706 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -23,10 +23,14 @@ var builder = WebApplication.CreateBuilder(args); builder.Logging.AddConsole(); -var azdTemplate= builder.Configuration["AZD_TEMPLATE"]; +var env = builder.Configuration["Environment"]; -if (azdTemplate == "enable"){ - // Configure SQL Server (Azd template) +if (env == "Local"){ + // Configure SQL Server (local) + Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); +} +else{ + // Configure SQL Server (prod) var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); builder.Services.AddDbContext(c => @@ -40,10 +44,6 @@ if (azdTemplate == "enable"){ options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); }); } -else{ - // Configure SQL Server (Locally) - Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); -} builder.Services.AddCookieSettings(); diff --git a/src/Web/appsettings.json b/src/Web/appsettings.json index 70989a6..0af1ab3 100644 --- a/src/Web/appsettings.json +++ b/src/Web/appsettings.json @@ -8,6 +8,7 @@ "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" }, "CatalogBaseUrl": "", + "Environment": "Local", "Logging": { "IncludeScopes": false, "LogLevel": { From 78dfbd70e1488503bf8f752f6bb64d7c6190f0f5 Mon Sep 17 00:00:00 2001 From: zedy Date: Fri, 17 Feb 2023 11:10:43 +0800 Subject: [PATCH 32/39] update variable --- infra/main.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index 096c853..f97b5fa 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -60,7 +60,7 @@ module web './core/host/appservice.bicep' = { AZURE_SQL_CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING' AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY: 'AZURE-SQL-IDENTITY-CONNECTION-STRING' AZURE_KEY_VAULT_ENDPOINT: keyVault.outputs.endpoint - AZD_TEMPLATE: 'enable' + Environment: 'Prod' } } } From a7b41991cb8355a7cba97e7b72d2a2b847477b33 Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 20 Feb 2023 16:29:08 +0800 Subject: [PATCH 33/39] use app.Environment.Isdevelopment to judge current env --- src/Web/Program.cs | 6 ++++-- src/Web/appsettings.json | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Web/Program.cs b/src/Web/Program.cs index a981706..9e35944 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -21,11 +21,13 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; var builder = WebApplication.CreateBuilder(args); +var detectEnvBuilder = WebApplication.CreateBuilder(args); + builder.Logging.AddConsole(); -var env = builder.Configuration["Environment"]; +var detectEnvApp = detectEnvBuilder.Build(); -if (env == "Local"){ +if (detectEnvApp.Environment.IsDevelopment() || detectEnvApp.Environment.EnvironmentName == "Docker"){ // Configure SQL Server (local) Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services); } diff --git a/src/Web/appsettings.json b/src/Web/appsettings.json index 0af1ab3..70989a6 100644 --- a/src/Web/appsettings.json +++ b/src/Web/appsettings.json @@ -8,7 +8,6 @@ "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" }, "CatalogBaseUrl": "", - "Environment": "Local", "Logging": { "IncludeScopes": false, "LogLevel": { From bd9b6181d1ae003a77feeabf017b148ceebd5713 Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 20 Feb 2023 16:29:26 +0800 Subject: [PATCH 34/39] use app.Environment.Isdevelopment to judge current env --- infra/main.bicep | 1 - 1 file changed, 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index f97b5fa..f1187e7 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -60,7 +60,6 @@ module web './core/host/appservice.bicep' = { AZURE_SQL_CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING' AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY: 'AZURE-SQL-IDENTITY-CONNECTION-STRING' AZURE_KEY_VAULT_ENDPOINT: keyVault.outputs.endpoint - Environment: 'Prod' } } } From 0c13ff9e5324eff6dbb31176dc6a5bb4e23fde5e Mon Sep 17 00:00:00 2001 From: zedy Date: Mon, 27 Mar 2023 13:34:34 +0800 Subject: [PATCH 35/39] update according to comments --- Directory.Packages.props | 2 +- README.md | 14 +- infra/core/database/sqlserver/sqlserver.bicep | 3 +- infra/core/host/appservice.bicep | 6 +- infra/core/security/keyvault-access.bicep | 2 +- src/Web/AzureDeveloperCliCredential.cs | 148 ------------------ 6 files changed, 19 insertions(+), 156 deletions(-) delete mode 100644 src/Web/AzureDeveloperCliCredential.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 4573d60..802924a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,7 +12,7 @@ - + diff --git a/README.md b/README.md index 272fb32..3caeab2 100644 --- a/README.md +++ b/README.md @@ -63,20 +63,28 @@ powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' curl -fsSL https://aka.ms/install-azd.sh | bash ``` +And you can also install with package managers, like winget, choco, and brew. For more detials, you can follow the documentation: https://aka.ms/azure-dev/install. + After logging in with the following command, you will be able to use the azd cli to quickly provision and deploy the application. ``` azd login ``` -Then, just use the `azd up` command to complete all operations of clone, provision and deployment. +Then, executes the commands `azd init` to initializes environment. ``` -azd up -t dotnet-architecture/eShopOnWeb +azd init -t dotnet-architecture/eShopOnWeb ``` + +And executes the commands `azd up` to complete all operations of provision and deployment. +``` +azd up +``` + According to the prompt, enter an `env name`, and select `subscription` and `location`, these are the necessary parameters when you create resources. Wait a moment for the resource deployment to complete, click the web endpoint and you will see the home page. **Notes:** -Considering security, we store its related data (id, password) in the key vault when we create the database, and obtain it from the key vault when we use it. This is different from directly deploying applications locally. +Considering security, we store its related data (id, password) in the **Azure Key Vault** when we create the database, and obtain it from the Key Vault when we use it. This is different from directly deploying applications locally. You can also run the sample directly locally (See below). diff --git a/infra/core/database/sqlserver/sqlserver.bicep b/infra/core/database/sqlserver/sqlserver.bicep index 1c4c212..64477a7 100644 --- a/infra/core/database/sqlserver/sqlserver.bicep +++ b/infra/core/database/sqlserver/sqlserver.bicep @@ -33,7 +33,7 @@ resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { resource firewall 'firewallRules' = { name: 'Azure Services' properties: { - // Allow all clients + // 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' @@ -127,4 +127,3 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}' output connectionStringKey string = connectionStringKey output databaseName string = sqlServer::database.name -output connectionString string = connectionString diff --git a/infra/core/host/appservice.bicep b/infra/core/host/appservice.bicep index 62e34a6..c65f2b8 100644 --- a/infra/core/host/appservice.bicep +++ b/infra/core/host/appservice.bicep @@ -32,6 +32,8 @@ 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 @@ -43,12 +45,14 @@ resource appService 'Microsoft.Web/sites@2022-03-01' = { siteConfig: { linuxFxVersion: linuxFxVersion alwaysOn: alwaysOn - ftpsState: 'FtpsOnly' + 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) } diff --git a/infra/core/security/keyvault-access.bicep b/infra/core/security/keyvault-access.bicep index 96c9cf7..aa989eb 100644 --- a/infra/core/security/keyvault-access.bicep +++ b/infra/core/security/keyvault-access.bicep @@ -1,6 +1,6 @@ param name string = 'add' -param keyVaultName string = '' +param keyVaultName string param permissions object = { secrets: [ 'get', 'list' ] } param principalId string diff --git a/src/Web/AzureDeveloperCliCredential.cs b/src/Web/AzureDeveloperCliCredential.cs deleted file mode 100644 index 406c7bf..0000000 --- a/src/Web/AzureDeveloperCliCredential.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Azure.Core; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text.Json; -using System.Text.RegularExpressions; - -namespace Azure.Identity -{ - public class AzureDeveloperCliCredential : TokenCredential - { - internal const string AzdCliNotInstalled = $"Azure Developer CLI could not be found. {Troubleshoot}"; - internal const string AzdNotLogIn = "Please run 'azd login' from a command prompt to authenticate before using this credential."; - internal const string WinAzdCliError = "'azd is not recognized"; - internal const string AzdCliTimeoutError = "Azure Developer CLI authentication timed out."; - internal const string AzdCliFailedError = "Azure Developer CLI authentication failed due to an unknown error."; - internal const string Troubleshoot = "Please visit https://aka.ms/azure-dev for installation instructions and then, once installed, authenticate to your Azure account using 'azd login'."; - internal const string InteractiveLoginRequired = "Azure Developer CLI could not login. Interactive login is required."; - private const string RefreshTokeExpired = "The provided authorization code or refresh token has expired due to inactivity. Send a new interactive authorization request for this user and resource."; - - private static readonly string DefaultWorkingDirWindows = Environment.GetFolderPath(Environment.SpecialFolder.System); - private const string DefaultWorkingDirNonWindows = "/bin/"; - private static readonly string DefaultWorkingDir = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DefaultWorkingDirWindows : DefaultWorkingDirNonWindows; - - private static readonly Regex AzdNotFoundPattern = new Regex("azd:(.*)not found"); - - public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken = default) - { - return RequestCliAccessTokenAsync(requestContext, cancellationToken) - .GetAwaiter() - .GetResult(); - } - - public override async ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken = default) - { - return await RequestCliAccessTokenAsync(requestContext, cancellationToken).ConfigureAwait(false); - } - - private async ValueTask RequestCliAccessTokenAsync(TokenRequestContext context, CancellationToken cancellationToken) - { - try - { - ProcessStartInfo processStartInfo = GetAzdCliProcessStartInfo(context.Scopes); - string output = await RunProcessAsync(processStartInfo); - - return DeserializeOutput(output); - } - catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) - { - throw new AuthenticationFailedException(AzdCliTimeoutError); - } - catch (InvalidOperationException exception) - { - bool isWinError = exception.Message.StartsWith(WinAzdCliError, StringComparison.CurrentCultureIgnoreCase); - bool isOtherOsError = AzdNotFoundPattern.IsMatch(exception.Message); - - if (isWinError || isOtherOsError) - { - throw new CredentialUnavailableException(AzdCliNotInstalled); - } - - bool isAADSTSError = exception.Message.Contains("AADSTS"); - bool isLoginError = exception.Message.IndexOf("azd login", StringComparison.OrdinalIgnoreCase) != -1; - - if (isLoginError && !isAADSTSError) - { - throw new CredentialUnavailableException(AzdNotLogIn); - } - - bool isRefreshTokenFailedError = exception.Message.IndexOf(AzdCliFailedError, StringComparison.OrdinalIgnoreCase) != -1 && - exception.Message.IndexOf(RefreshTokeExpired, StringComparison.OrdinalIgnoreCase) != -1 || - exception.Message.IndexOf("CLIInternalError", StringComparison.OrdinalIgnoreCase) != -1; - - if (isRefreshTokenFailedError) - { - throw new CredentialUnavailableException(InteractiveLoginRequired); - } - - throw new AuthenticationFailedException($"{AzdCliFailedError} {Troubleshoot} {exception.Message}"); - } - catch (Exception ex) - { - throw new CredentialUnavailableException($"{AzdCliFailedError} {Troubleshoot} {ex.Message}"); - } - } - - private async ValueTask RunProcessAsync(ProcessStartInfo processStartInfo, CancellationToken cancellationToken = default) - { - var process = Process.Start(processStartInfo); - if (process == null) - { - throw new CredentialUnavailableException(AzdCliFailedError); - } - - await process.WaitForExitAsync(cancellationToken); - - if (process.ExitCode != 0) - { - var errorMessage = process.StandardError.ReadToEnd(); - throw new InvalidOperationException(errorMessage); - } - - return process.StandardOutput.ReadToEnd(); - } - - private ProcessStartInfo GetAzdCliProcessStartInfo(string[] scopes) - { - string scopeArgs = string.Join(" ", scopes.Select(scope => string.Format($"--scope {scope}"))); - string command = $"azd auth token --output json {scopeArgs}"; - - string fileName; - string argument; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"); - argument = $"/c \"{command}\""; - } - else - { - fileName = "/bin/sh"; - argument = $"-c \"{command}\""; - } - - return new ProcessStartInfo - { - FileName = fileName, - Arguments = argument, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - ErrorDialog = false, - CreateNoWindow = true, - WorkingDirectory = DefaultWorkingDir, - }; - } - - private static AccessToken DeserializeOutput(string output) - { - using JsonDocument document = JsonDocument.Parse(output); - - JsonElement root = document.RootElement; - string accessToken = root.GetProperty("token").GetString(); - DateTimeOffset expiresOn = root.GetProperty("expiresOn").GetDateTimeOffset(); - - return new AccessToken(accessToken, expiresOn); - } - } -} From 890f6095427ab80dd313f65da86084bb9dc5808c Mon Sep 17 00:00:00 2001 From: zedy Date: Tue, 28 Mar 2023 09:40:23 +0800 Subject: [PATCH 36/39] correct readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3caeab2..7dd7726 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' curl -fsSL https://aka.ms/install-azd.sh | bash ``` -And you can also install with package managers, like winget, choco, and brew. For more detials, you can follow the documentation: https://aka.ms/azure-dev/install. +And you can also install with package managers, like winget, choco, and brew. For more details, you can follow the documentation: https://aka.ms/azure-dev/install. After logging in with the following command, you will be able to use the azd cli to quickly provision and deploy the application. @@ -71,12 +71,12 @@ After logging in with the following command, you will be able to use the azd cli azd login ``` -Then, executes the commands `azd init` to initializes environment. +Then, execute the `azd init` command to initialize the environment. ``` azd init -t dotnet-architecture/eShopOnWeb ``` -And executes the commands `azd up` to complete all operations of provision and deployment. +Run `azd up` to provision all the resources to Azure and deploy the code to those resources. ``` azd up ``` From d2753c7d68446eeee5c208f3dd02dd39e63463be Mon Sep 17 00:00:00 2001 From: zedy Date: Fri, 11 Aug 2023 13:30:14 +0800 Subject: [PATCH 37/39] fix possible null reference error --- src/Web/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Web/Program.cs b/src/Web/Program.cs index d67117a..af23e7f 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -34,15 +34,15 @@ if (detectEnvApp.Environment.IsDevelopment() || detectEnvApp.Environment.Environ else{ // Configure SQL Server (prod) var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential()); - builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential); + builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"] ?? ""), credential); builder.Services.AddDbContext(c => { - var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"]]; + var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CATALOG_CONNECTION_STRING_KEY"] ?? ""]; c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); }); builder.Services.AddDbContext(options => { - var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"]]; + var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY"] ?? ""]; options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()); }); } From 890e6df1ebc15c0b08319754a69fa1efffd029ec Mon Sep 17 00:00:00 2001 From: zedy Date: Thu, 17 Aug 2023 10:24:09 +0800 Subject: [PATCH 38/39] update azd login to azd auth login --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4db165..a8109c5 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ And you can also install with package managers, like winget, choco, and brew. Fo After logging in with the following command, you will be able to use the azd cli to quickly provision and deploy the application. ``` -azd login +azd auth login ``` Then, execute the `azd init` command to initialize the environment. From 180b6e1f2d2b7d3f412d842f1d23df4d5e57b99e Mon Sep 17 00:00:00 2001 From: zedy Date: Wed, 23 Aug 2023 10:58:31 +0800 Subject: [PATCH 39/39] add a note in the instruction steps --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c4db165..616fc83 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,8 @@ azd up According to the prompt, enter an `env name`, and select `subscription` and `location`, these are the necessary parameters when you create resources. Wait a moment for the resource deployment to complete, click the web endpoint and you will see the home page. **Notes:** -Considering security, we store its related data (id, password) in the **Azure Key Vault** when we create the database, and obtain it from the Key Vault when we use it. This is different from directly deploying applications locally. +1. Considering security, we store its related data (id, password) in the **Azure Key Vault** when we create the database, and obtain it from the Key Vault when we use it. This is different from directly deploying applications locally. +2. The resource group name created in azure portal will be **rg-{env name}**. You can also run the sample directly locally (See below).