diff --git a/samples/web-app-cosmosdb-mongodb-api/python/README.md b/samples/web-app-cosmosdb-mongodb-api/python/README.md index 183f120..c2fadfc 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/README.md +++ b/samples/web-app-cosmosdb-mongodb-api/python/README.md @@ -8,9 +8,23 @@ The following diagram illustrates the architecture of the solution:  -- **Azure Web App**: Hosts the Python Flask application -- **Azure App Service Plan**: Provides compute resources for the web app -- **Azure CosmosDB for MongoDB**: Stores activity data in a MongoDB collection +The web app enables users to plan and manage vacation activities, with all data persisted in a CosmosDB-backed MongoDB collection. The solution is composed of the following Azure resources: + +1. [Azure Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli): A logical container scoping all resources in this sample. +2. [Azure Virtual Network](https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview): Hosts two subnets: + - *app-subnet*: Dedicated to [regional VNet integration](https://learn.microsoft.com/azure/azure-functions/functions-networking-options?tabs=azure-portal#outbound-networking-features) with the Function App. + - *pe-subnet*: Used for hosting Azure Private Endpoints. +3. [Azure Private DNS Zone](https://learn.microsoft.com/azure/dns/private-dns-privatednszone): Handles DNS resolution for the CosmosDB for MongoDB Private Endpoint within the virtual network. +4. [Azure Private Endpoint](https://learn.microsoft.com/azure/private-link/private-endpoint-overview): Secures network access to the CosmosDB for MongoDB account via a private IP within the VNet. +5. [Azure NAT Gateway](https://learn.microsoft.com/azure/nat-gateway/nat-overview): Provides deterministic outbound connectivity for the Web App. Included for completeness; the sample app does not call any external services. +6. [Azure Network Security Group](https://learn.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview): Enforces inbound and outbound traffic rules across the virtual network's subnets. +7. [Azure Log Analytics Workspace](https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-overview): Centralizes diagnostic logs and metrics from all resources in the solution. +8. [Azure Cosmos DB for MongoDB](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/introduction): A globally distributed database account optimized for MongoDB workloads, with multi-region failover enabled. +9. [MongoDB Database](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `sampledb` database that holds all application data. +10. [MongoDB Collection](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `activities` collection within `sampledb`, used to store vacation activity records. +11. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans): The underlying compute tier that hosts the web application. +12. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview): Runs the Python Flask single-page application (*Vacation Planner*), connected to CosmosDB for MongoDB via VNet integration. +13. [App Service Source Control](https://learn.microsoft.com/en-us/rest/api/appservice/web-apps/create-or-update-source-control?view=rest-appservice-2024-11-01): *(Optional)* Configures continuous deployment from a public GitHub repository. ## Prerequisites diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/README.md b/samples/web-app-cosmosdb-mongodb-api/python/bicep/README.md index e212e56..5629ea3 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/bicep/README.md +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/README.md @@ -27,16 +27,25 @@ For more information, see [Get started with the az tool on LocalStack](https://a ## Architecture Overview -The [deploy.sh](deploy.sh) script creates the [Azure Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli) for all the Azure resources, while the [main.bicep](main.bicep) Bicep module creates the following Azure resources: - -1. [Azure CosmosDB Account (MongoDB API)](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/introduction): A globally distributed database account configured for MongoDB workloads, with multi-region failover. -2. [MongoDB Database](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `sampledb` database for storing application data. -3. [MongoDB Collection](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `activities` collection within `sampledb` for storing vacation activity records. -4. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans): The compute resource that hosts the web application. -5. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview): Hosts the Python Flask single-page application (*Vacation Planner*), connected to CosmosDB for MongoDB. -6. [App Service Source Control](https://learn.microsoft.com/en-us/rest/api/appservice/web-apps/create-or-update-source-control?view=rest-appservice-2024-11-01): (Optional) Configures automatic deployment from a public GitHub repository. - -The web app allows users to plan and manage vacation activities, storing all activity data in a MongoDB collection. +The [deploy.sh](deploy.sh) script creates the [Azure Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli) for all the Azure resources, while the Bicep modules create the following Azure resources: + +1. [Azure Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli): A logical container scoping all resources in this sample. +2. [Azure Virtual Network](https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview): Hosts two subnets: + - *app-subnet*: Dedicated to [regional VNet integration](https://learn.microsoft.com/azure/azure-functions/functions-networking-options?tabs=azure-portal#outbound-networking-features) with the Function App. + - *pe-subnet*: Used for hosting Azure Private Endpoints. +3. [Azure Private DNS Zone](https://learn.microsoft.com/azure/dns/private-dns-privatednszone): Handles DNS resolution for the CosmosDB for MongoDB Private Endpoint within the virtual network. +4. [Azure Private Endpoint](https://learn.microsoft.com/azure/private-link/private-endpoint-overview): Secures network access to the CosmosDB for MongoDB account via a private IP within the VNet. +5. [Azure NAT Gateway](https://learn.microsoft.com/azure/nat-gateway/nat-overview): Provides deterministic outbound connectivity for the Web App. Included for completeness; the sample app does not call any external services. +6. [Azure Network Security Group](https://learn.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview): Enforces inbound and outbound traffic rules across the virtual network's subnets. +7. [Azure Log Analytics Workspace](https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-overview): Centralizes diagnostic logs and metrics from all resources in the solution. +8. [Azure Cosmos DB for MongoDB](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/introduction): A globally distributed database account optimized for MongoDB workloads, with multi-region failover enabled. +9. [MongoDB Database](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `sampledb` database that holds all application data. +10. [MongoDB Collection](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `activities` collection within `sampledb`, used to store vacation activity records. +11. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans): The underlying compute tier that hosts the web application. +12. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview): Runs the Python Flask single-page application (*Vacation Planner*), connected to CosmosDB for MongoDB via VNet integration. +13. [App Service Source Control](https://learn.microsoft.com/en-us/rest/api/appservice/web-apps/create-or-update-source-control?view=rest-appservice-2024-11-01): *(Optional)* Configures continuous deployment from a public GitHub repository. + +The web app enables users to plan and manage vacation activities, with all data persisted in a CosmosDB-backed MongoDB collection. For more information on the sample application, see [Azure Web App with Azure CosmosDB for MongoDB](../README.md). ## Configuration @@ -104,12 +113,27 @@ Run the deployment script: ## Validation -After deployment, you can use the `validate.sh` script to verify that all resources were created and configured correctly: +Once the deployment completes, run the [validate.sh](../scripts/validate.sh) script to confirm that all resources were provisioned and configured as expected: ```bash #!/bin/bash # Variables +PREFIX='local' +SUFFIX='test' +RESOURCE_GROUP_NAME="${PREFIX}-rg" +LOG_ANALYTICS_NAME="${PREFIX}-log-analytics-${SUFFIX}" +WEBAPP_SUBNET_NSG_NAME="${PREFIX}-webapp-subnet-nsg-${SUFFIX}" +PE_SUBNET_NSG_NAME="${PREFIX}-pe-subnet-nsg-${SUFFIX}" +NAT_GATEWAY_NAME="${PREFIX}-nat-gateway-${SUFFIX}" +VIRTUAL_NETWORK_NAME="${PREFIX}-vnet-${SUFFIX}" +PRIVATE_DNS_ZONE_NAME="privatelink.mongo.cosmos.azure.com" +PRIVATE_ENDPOINT_NAME="${PREFIX}-mongodb-pe-${SUFFIX}" +APP_SERVICE_PLAN_NAME="${PREFIX}-app-service-plan-${SUFFIX}" +WEBAPP_NAME="${PREFIX}-webapp-${SUFFIX}" +COSMOSDB_ACCOUNT_NAME="${PREFIX}-mongodb-${SUFFIX}" +MONGODB_DATABASE_NAME="sampledb" +COLLECTION_NAME="activities" ENVIRONMENT=$(az account show --query environmentName --output tsv) # Choose the appropriate CLI based on the environment @@ -122,41 +146,121 @@ else fi # Check resource group +echo -e "[$RESOURCE_GROUP_NAME] resource group:\n" $AZ group show \ ---name local-rg \ ---output table - -# List resources -$AZ resource list \ ---resource-group local-rg \ ---output table + --name "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check App Service Plan +echo -e "\n[$APP_SERVICE_PLAN_NAME] app service plan:\n" +$AZ appservice plan show \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --name "$APP_SERVICE_PLAN_NAME" \ + --output table \ + --only-show-errors # Check Azure Web App +echo -e "\n[$WEBAPP_NAME] web app:\n" $AZ webapp show \ ---name local-webapp-test \ ---resource-group local-rg \ ---output table + --name "$WEBAPP_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors # Check Azure CosmosDB account +echo -e "\n[$COSMOSDB_ACCOUNT_NAME] cosmosdb account:\n" $AZ cosmosdb show \ ---name local-mongodb-test \ ---resource-group local-rg \ ---output table + --name "$COSMOSDB_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query '{Name:name,Location:location,ResourceGroup:resourceGroup,DocumentEndpoint:documentEndpoint}' \ + --output table \ + --only-show-errors # Check MongoDB database +echo -e "\n[$MONGODB_DATABASE_NAME] mongodb database:\n" $AZ cosmosdb mongodb database show \ ---name sampledb \ ---account-name local-mongodb-test \ ---resource-group local-rg \ ---output table + --name "$MONGODB_DATABASE_NAME" \ + --account-name "$COSMOSDB_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query '{Name:name,ResourceGroup:resourceGroup}' \ + --output table \ + --only-show-errors # Check MongoDB collection +echo -e "\n[$COLLECTION_NAME] mongodb collection:\n" $AZ cosmosdb mongodb collection show \ ---name activities \ ---database-name sampledb \ ---account-name local-mongodb-test \ ---resource-group local-rg \ ---output table + --name "$COLLECTION_NAME" \ + --database-name "$MONGODB_DATABASE_NAME" \ + --account-name "$COSMOSDB_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Log Analytics Workspace +echo -e "\n[$LOG_ANALYTICS_NAME] log analytics workspace:\n" +$AZ monitor log-analytics workspace show \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --workspace-name "$LOG_ANALYTICS_NAME" \ + --query '{Name:name,Location:location,ResourceGroup:resourceGroup}' \ + --output table \ + --only-show-errors + +# Check NAT Gateway +echo -e "\n[$NAT_GATEWAY_NAME] nat gateway:\n" +$AZ network nat gateway show \ + --name "$NAT_GATEWAY_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Virtual Network +echo -e "\n[$VIRTUAL_NETWORK_NAME] virtual network:\n" +$AZ network vnet show \ + --name "$VIRTUAL_NETWORK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Private DNS Zone +echo -e "\n[$PRIVATE_DNS_ZONE_NAME] private dns zone:\n" +$AZ network private-dns zone show \ + --name "$PRIVATE_DNS_ZONE_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query '{Name:name,ResourceGroup:resourceGroup,RecordSets:recordSets,VirtualNetworkLinks:virtualNetworkLinks}' \ + --output table \ + --only-show-errors + +# Check Private Endpoint +echo -e "\n[$PRIVATE_ENDPOINT_NAME] private endpoint:\n" +$AZ network private-endpoint show \ + --name "$PRIVATE_ENDPOINT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Web App Subnet NSG +echo -e "\n[$WEBAPP_SUBNET_NSG_NAME] network security group:\n" +$AZ network nsg show \ + --name "$WEBAPP_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Private Endpoint Subnet NSG +echo -e "\n[$PE_SUBNET_NSG_NAME] network security group:\n" +$AZ network nsg show \ + --name "$PE_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# List resources +echo -e "\n[$RESOURCE_GROUP_NAME] all resources:\n" +$AZ resource list \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors ``` ## Cleanup diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/deploy.sh b/samples/web-app-cosmosdb-mongodb-api/python/bicep/deploy.sh index be12fa8..b119d5a 100755 --- a/samples/web-app-cosmosdb-mongodb-api/python/bicep/deploy.sh +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/deploy.sh @@ -1,11 +1,11 @@ #!/bin/bash # Variables -PREFIX='local' +PREFIX='bicep' SUFFIX='test' TEMPLATE="main.bicep" PARAMETERS="main.bicepparam" -RESOURCE_GROUP_NAME="${PREFIX}-webapp-cosmos-rg" +RESOURCE_GROUP_NAME="${PREFIX}-rg" LOCATION="westeurope" VALIDATE_TEMPLATE=1 USE_WHAT_IF=0 @@ -103,13 +103,15 @@ if DEPLOYMENT_OUTPUTS=$($AZ deployment group create \ prefix=$PREFIX \ suffix=$SUFFIX \ --query 'properties.outputs' -o json); then + # Extract only the JSON portion (everything from first { to the end) + DEPLOYMENT_JSON=$(echo "$DEPLOYMENT_OUTPUTS" | sed -n '/{/,$ p') echo "Bicep template [$TEMPLATE] deployed successfully. Outputs:" - echo "$DEPLOYMENT_OUTPUTS" | jq . - WEB_APP_NAME=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r '.webAppName.value') - ACCOUNT_NAME=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r '.accountName.value') - DATABASE_NAME=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r '.databaseName.value') - COLLECTION_NAME=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r '.collectionName.value') - DOCUMENT_ENDPOINT=$(echo "$DEPLOYMENT_OUTPUTS" | jq -r '.documentEndpoint.value') + echo "$DEPLOYMENT_JSON" | jq . + WEB_APP_NAME=$(echo "$DEPLOYMENT_JSON" | jq -r '.webAppName.value') + ACCOUNT_NAME=$(echo "$DEPLOYMENT_JSON" | jq -r '.accountName.value') + DATABASE_NAME=$(echo "$DEPLOYMENT_JSON" | jq -r '.databaseName.value') + COLLECTION_NAME=$(echo "$DEPLOYMENT_JSON" | jq -r '.collectionName.value') + DOCUMENT_ENDPOINT=$(echo "$DEPLOYMENT_JSON" | jq -r '.documentEndpoint.value') echo "Deployment details:" echo "Web App Name: $WEB_APP_NAME" echo "Database Account Name: $ACCOUNT_NAME" @@ -158,3 +160,7 @@ $AZ webapp deploy \ if [ -f "$ZIPFILE" ]; then rm "$ZIPFILE" fi + +# Print the list of resources in the resource group +echo "Listing resources in resource group [$RESOURCE_GROUP_NAME]..." +az resource list --resource-group "$RESOURCE_GROUP_NAME" --output table \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/main.bicep b/samples/web-app-cosmosdb-mongodb-api/python/bicep/main.bicep index db63dee..9699914 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/bicep/main.bicep +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/main.bicep @@ -1,3 +1,6 @@ +//******************************************** +// Parameters +//******************************************** @description('Specifies the prefix for the name of the Azure resources.') @minLength(2) param prefix string = take(uniqueString(resourceGroup().id), 4) @@ -120,8 +123,6 @@ param httpsOnly bool = false @description('Specifies the minimum TLS version for the Azure Web App.') @allowed([ - '1.0' - '1.1' '1.2' '1.3' ]) @@ -137,12 +138,6 @@ param publicNetworkAccess string = 'Enabled' @description('Specifies the optional Git Repo URL.') param repoUrl string = ' ' -@description('Specifies the tags to be applied to the resources.') -param tags object = { - environment: 'test' - iac: 'bicep' -} - @description('Specifies the primary replica region for the Cosmos DB account.') param primaryRegion string = 'westeurope' @@ -206,161 +201,205 @@ param mongoDbIndexKeys array = ['_id','username', 'activity', 'timestamp'] @description('Specifies the username for the application.') param username string = 'paolo' +@description('Specifies the name of the virtual network.') +param virtualNetworkName string = '' + +@description('Specifies the address prefixes of the virtual network.') +param virtualNetworkAddressPrefixes string = '10.0.0.0/8' + +@description('Specifies the name of the subnet used by the Web App for the regional virtual network integration.') +param webAppSubnetName string = 'app-subnet' + +@description('Specifies the address prefix of the subnet used by the Web App for the regional virtual network integration.') +param webAppSubnetAddressPrefix string = '10.0.0.0/24' + +@description('Specifies the name of the network security group associated to the subnet hosting the Web App.') +param webAppSubnetNsgName string = '' + +@description('Specifies the name of the subnet which contains the private endpoint to the Azure CosmosDB for MongoDB API account.') +param peSubnetName string = 'pe-subnet' + +@description('Specifies the address prefix of the subnet which contains the private endpoint to the Azure CosmosDB for MongoDB API account.') +param peSubnetAddressPrefix string = '10.0.1.0/24' + +@description('Specifies the name of the network security group associated to the subnet hosting the private endpoint to the Azure CosmosDB for MongoDB API account.') +param peSubnetNsgName string = '' + +@description('Specifies the length of the Public IP Prefix.') +@minValue(28) +@maxValue(32) +param natGatewayPublicIpPrefixLength int = 31 + +@description('Specifies the name of the Azure NAT Gateway.') +param natGatewayName string = '' + +@description('Specifies a list of availability zones denoting the zone in which Nat Gateway should be deployed.') +param natGatewayZones array = [] + +@description('Specifies the idle timeout in minutes for the Azure NAT Gateway.') +param natGatewayIdleTimeoutMins int = 30 + +@description('Specifies the name of the private endpoint to the Azure CosmosDB for MongoDB API account.') +param cosmosDbPrivateEndpointName string = '' + +@description('Specifies the name of the Azure Log Analytics resource.') +param logAnalyticsName string = '' + +@description('Specifies the service tier of the workspace: Free, Standalone, PerNode, Per-GB.') +@allowed([ + 'Free' + 'Standalone' + 'PerNode' + 'PerGB2018' +]) +param logAnalyticsSku string = 'PerNode' + +@description('Specifies the workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku. 730 days is the maximum allowed for all other Skus.') +param logAnalyticsRetentionInDays int = 60 + +@description('Specifies the tags to be applied to the resources.') +param tags object = { + environment: 'test' + iac: 'bicep' +} + +//******************************************** +// Variables +//******************************************** var webAppName = '${prefix}-webapp-${suffix}' -var appServicePlanPortalName = '${prefix}-app-service-plan-${suffix}' +var appServicePlanName = '${prefix}-app-service-plan-${suffix}' var accountName = '${prefix}-mongodb-${suffix}' -var consistencyPolicy = { - Eventual: { - defaultConsistencyLevel: 'Eventual' - } - ConsistentPrefix: { - defaultConsistencyLevel: 'ConsistentPrefix' - } - Session: { - defaultConsistencyLevel: 'Session' - } - BoundedStaleness: { - defaultConsistencyLevel: 'BoundedStaleness' - maxStalenessPrefix: maxStalenessPrefix - maxIntervalInSeconds: maxIntervalInSeconds - } - Strong: { - defaultConsistencyLevel: 'Strong' - } -} -var locations = [ - { - locationName: primaryRegion - failoverPriority: 0 - isZoneRedundant: false - } - { - locationName: secondaryRegion - failoverPriority: 1 - isZoneRedundant: false - } -] - -resource appServicePlan 'Microsoft.Web/serverfarms@2024-11-01' = { - name: appServicePlanPortalName - location: location - tags: tags - kind: appServicePlanKind - sku: { - tier: skuTier - name: skuName - } - properties: { - reserved: reserved - zoneRedundant: zoneRedundant - maximumElasticWorkerCount: skuTier == 'FlexConsumption' ? 1 : 20 + +//******************************************** +// Modules and Resources +//******************************************** +module workspace 'modules/log-analytics.bicep' = { + name: 'workspace' + params: { + // properties + name: empty(logAnalyticsName) ? toLower('${prefix}-log-analytics-${suffix}') : logAnalyticsName + location: location + tags: tags + sku: logAnalyticsSku + retentionInDays: logAnalyticsRetentionInDays } } -resource webApp 'Microsoft.Web/sites@2024-11-01' = { - name: webAppName - location: location - tags: tags - kind: webAppKind - properties: { - httpsOnly: httpsOnly - serverFarmId: appServicePlan.id - siteConfig: { - linuxFxVersion: toUpper('${runtimeName}|${runtimeVersion}') - minTlsVersion: minTlsVersion - publicNetworkAccess: publicNetworkAccess - } - } - identity: { - type: 'SystemAssigned' +module mongoDb 'modules/mongo-db.bicep' = { + name: 'mongoDb' + params: { + name: accountName + location: location + primaryRegion: primaryRegion + secondaryRegion: secondaryRegion + defaultConsistencyLevel: defaultConsistencyLevel + serverVersion: serverVersion + maxStalenessPrefix: maxStalenessPrefix + maxIntervalInSeconds: maxIntervalInSeconds + databaseName: databaseName + sharedThroughput: sharedThroughput + collectionName: collectionName + dedicatedThroughput: dedicatedThroughput + mongoDbIndexKeys: mongoDbIndexKeys + workspaceId: workspace.outputs.id + tags: tags } } -resource configAppSettings 'Microsoft.Web/sites/config@2024-11-01' = { - parent: webApp - name: 'appsettings' - properties: { - SCM_DO_BUILD_DURING_DEPLOYMENT: 'true' - ENABLE_ORYX_BUILD: 'true' - COSMOSDB_CONNECTION_STRING: account.listConnectionStrings().connectionStrings[0].connectionString - COSMOSDB_DATABASE_NAME: databaseName - COSMOSDB_COLLECTION_NAME: collectionName - LOGIN_NAME: username +module network 'modules/virtual-network.bicep' = { + name: 'network' + params: { + virtualNetworkName: empty(virtualNetworkName) ? toLower('${prefix}-vnet-${suffix}') : virtualNetworkName + virtualNetworkAddressPrefixes: virtualNetworkAddressPrefixes + webAppSubnetName: webAppSubnetName + webAppSubnetAddressPrefix: webAppSubnetAddressPrefix + webAppSubnetNsgName: empty(webAppSubnetNsgName) ? toLower('${prefix}-webapp-subnet-nsg-${suffix}') : webAppSubnetNsgName + peSubnetName: peSubnetName + peSubnetAddressPrefix: peSubnetAddressPrefix + peSubnetNsgName: empty(peSubnetNsgName) ? toLower('${prefix}-pe-subnet-nsg-${suffix}') : peSubnetNsgName + natGatewayName: empty(natGatewayName) ? toLower('${prefix}-nat-gateway-${suffix}') : natGatewayName + natGatewayZones: natGatewayZones + natGatewayPublicIpPrefixName: toLower('${prefix}-nat-gateway-pip-prefix-${suffix}') + natGatewayPublicIpPrefixLength: natGatewayPublicIpPrefixLength + natGatewayIdleTimeoutMins: natGatewayIdleTimeoutMins + delegationServiceName: skuTier == 'FlexConsumption' ? 'Microsoft.App/environments' : 'Microsoft.Web/serverfarms' + workspaceId: workspace.outputs.id + location: location + tags: tags } - dependsOn: [ - collection - ] } -resource webAppSourceControl 'Microsoft.Web/sites/sourcecontrols@2024-11-01' = if (contains(repoUrl,'http')){ - name: 'web' - parent: webApp - properties: { - repoUrl: repoUrl - branch: 'master' - isManualIntegration: true +module privateDnsZone 'modules/private-dns-zone.bicep' = { + name: 'privateDnsZone' + params: { + name: 'privatelink.mongo.cosmos.azure.com' + vnetId: network.outputs.virtualNetworkId + tags: tags } } -resource account 'Microsoft.DocumentDB/databaseAccounts@2025-04-15' = { - name: toLower(accountName) - location: location - kind: 'MongoDB' - properties: { - consistencyPolicy: consistencyPolicy[defaultConsistencyLevel] - locations: locations - databaseAccountOfferType: 'Standard' - enableAutomaticFailover: true - apiProperties: { - serverVersion: serverVersion - } - capabilities: [ - { - name: 'DisableRateLimitingResponses' - } +module privateEndpoints 'modules/private-endpoint.bicep' = { + name: 'privateEndpoints' + params: { + name: empty(cosmosDbPrivateEndpointName) + ? toLower('${prefix}-mongodb-pe-${suffix}') + : cosmosDbPrivateEndpointName + privateLinkServiceId: mongoDb.outputs.id + privateDnsZoneId: privateDnsZone.outputs.id + vnetId: network.outputs.virtualNetworkId + subnetId: network.outputs.peSubnetId + groupIds: [ + 'mongodb' ] + location: location + tags: tags } } -resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15' = { - parent: account - name: databaseName - properties: { - resource: { - id: databaseName - } - options: { - throughput: sharedThroughput - } +module appServicePlan 'modules/app-service-plan.bicep' = { + name: 'appServicePlan' + params: { + name: appServicePlanName + location: location + skuName: skuName + skuTier: skuTier + kind: appServicePlanKind + reserved: reserved + zoneRedundant: zoneRedundant + workspaceId: workspace.outputs.id + tags: tags } } -resource collection 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15' = { - parent: database - name: collectionName - properties: { - resource: { - id: collectionName - shardKey: { - username: 'Hash' - } - // Use a for loop to dynamically create the 'indexes' array based on the 'mongoDbIndexKeys' parameter - indexes: [for key in mongoDbIndexKeys: { - key: { - keys: [ - key - ] - } - }] - } - options: { - throughput: dedicatedThroughput - } +module webApp 'modules/web-app.bicep' = { + name: webAppName + params: { + name: webAppName + location: location + kind: webAppKind + httpsOnly: httpsOnly + runtimeName: runtimeName + runtimeVersion: runtimeVersion + minTlsVersion: minTlsVersion + publicNetworkAccess: publicNetworkAccess + repoUrl: repoUrl + virtualNetworkName: network.outputs.virtualNetworkName + subnetName: network.outputs.webAppSubnetName + hostingPlanName: appServicePlan.outputs.name + accountName: mongoDb.outputs.name + databaseName: mongoDb.outputs.databaseName + collectionName: mongoDb.outputs.collectionName + username: username + workspaceId: workspace.outputs.id + tags: tags } } -output webAppName string = webAppName -output accountName string = accountName -output databaseName string = databaseName +//******************************************** +// Outputs +//******************************************** +output webAppName string = webApp.outputs.name +output accountName string = mongoDb.outputs.name +output databaseName string = mongoDb.outputs.databaseName output collectionName string = collectionName -output documentEndpoint string = account.properties.documentEndpoint +output documentEndpoint string = mongoDb.outputs.documentEndpoint diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/app-service-plan.bicep b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/app-service-plan.bicep new file mode 100644 index 0000000..4b5cfb3 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/app-service-plan.bicep @@ -0,0 +1,154 @@ +//******************************************** +// Parameters +//******************************************** +@description('Specifies the name of the App Service Plan.') +param name string + +@description('Specifies the location.') +param location string = resourceGroup().location + +@description('Specifies the tier name for the hosting plan.') +@allowed([ + 'Basic' + 'Standard' + 'ElasticPremium' + 'Premium' + 'PremiumV2' + 'Premium0V3' + 'PremiumV3' + 'PremiumMV3' + 'Isolated' + 'IsolatedV2' + 'WorkflowStandard' + 'FlexConsumption' +]) +param skuTier string = 'Standard' + +@description('Specifies the SKU name for the hosting plan.') +@allowed([ + 'B1' + 'B2' + 'B3' + 'S1' + 'S2' + 'S3' + 'EP1' + 'EP2' + 'EP3' + 'P1' + 'P2' + 'P3' + 'P1V2' + 'P2V2' + 'P3V2' + 'P0V3' + 'P1V3' + 'P2V3' + 'P3V3' + 'P1MV3' + 'P2MV3' + 'P3MV3' + 'P4MV3' + 'P5MV3' + 'I1' + 'I2' + 'I3' + 'I1V2' + 'I2V2' + 'I3V2' + 'I4V2' + 'I5V2' + 'I6V2' + 'WS1' + 'WS2' + 'WS3' + 'FC1' +]) +param skuName string = 'S1' + +@description('Specifies the kind of the hosting plan.') +@allowed([ + 'app' + 'elastic' + 'functionapp' + 'windows' + 'linux' +]) +param kind string = 'linux' + +@description('Specifies whether the hosting plan is reserved.') +param reserved bool = true + +@description('Specifies whether the hosting plan is zone redundant.') +param zoneRedundant bool = false + +@description('Specifies the resource id of the Log Analytics workspace.') +param workspaceId string + +@description('Specifies the tags to be applied to the resources.') +param tags object = {} + +//******************************************** +// Variables +//******************************************** + +var diagnosticSettingsName = 'default' +var logCategories = [] +var metricCategories = [ + 'AllMetrics' +] +var logs = [ + for category in logCategories: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: 0 + } + } +] +var metrics = [ + for category in metricCategories: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: 0 + } + } +] + +//******************************************** +// Resources +//******************************************** +resource appServicePlan 'Microsoft.Web/serverfarms@2024-11-01' = { + name: name + location: location + tags: tags + kind: kind + sku: { + tier: skuTier + name: skuName + } + properties: { + reserved: reserved + zoneRedundant: zoneRedundant + maximumElasticWorkerCount: skuTier == 'FlexConsumption' ? 1 : 20 + } +} + +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if(!empty(workspaceId)) { + name: diagnosticSettingsName + scope: appServicePlan + properties: { + workspaceId: workspaceId + logs: logs + metrics: metrics + } +} + +//******************************************** +// Outputs +//******************************************** +output id string = appServicePlan.id +output name string = appServicePlan.name diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/log-analytics.bicep b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/log-analytics.bicep new file mode 100644 index 0000000..2618829 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/log-analytics.bicep @@ -0,0 +1,45 @@ +//******************************************** +// Parameters +//******************************************** +@description('Specifies the name of the Log Analytics workspace.') +param name string + +@description('Specifies the location.') +param location string = resourceGroup().location + +@description('Specifies the service tier of the workspace: Free, Standalone, PerNode, Per-GB.') +@allowed([ + 'Free' + 'Standalone' + 'PerNode' + 'PerGB2018' +]) +param sku string = 'PerNode' + +@description('Specifies the workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku. 730 days is the maximum allowed for all other Skus.') +param retentionInDays int = 60 + +@description('Specifies the resource tags.') +param tags object + +//******************************************** +// Resources +//******************************************** +resource workspace 'Microsoft.OperationalInsights/workspaces@2025-07-01' = { + name: name + tags: tags + location: location + properties: { + sku: { + name: sku + } + retentionInDays: retentionInDays + } +} + +//******************************************** +// Outputs +//******************************************** +output id string = workspace.id +output name string = workspace.name +output customerId string = workspace.properties.customerId diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/mongo-db.bicep b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/mongo-db.bicep new file mode 100644 index 0000000..b95aef4 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/mongo-db.bicep @@ -0,0 +1,217 @@ +//******************************************** +// Parameters +//******************************************** +@description('Specifies a globally unique name the Azure Web App.') +param name string + +@description('Specifies the location for all resources.') +param location string = resourceGroup().location + +@description('Specifies the name for the Mongo DB database.') +param databaseName string = 'sampledb' + +@minValue(400) +@maxValue(1000000) +@description('Specifies the shared throughput for the Mongo DB database, up to 25 collections.') +param sharedThroughput int = 400 + +@description('Specifies the name for the Mongo DB collection.') +param collectionName string = 'activities' + +@minValue(400) +@maxValue(1000000) +@description('Specifies the dedicated throughput for the Mongo DB collection.') +param dedicatedThroughput int = 400 + +@description('Specifies a list of field names for which to create single-field indexes on the MongoDB collection.') +param mongoDbIndexKeys array = ['_id','username', 'activity', 'timestamp'] + +@description('Specifies the primary replica region for the Cosmos DB account.') +param primaryRegion string = 'westeurope' + +@description('Specifies the secondary replica region for the Cosmos DB account.') +param secondaryRegion string = 'northeurope' + +@allowed([ + 'Eventual' + 'ConsistentPrefix' + 'Session' + 'BoundedStaleness' + 'Strong' +]) +@description('Specifies the default consistency level of the Cosmos DB account.') +param defaultConsistencyLevel string = 'Eventual' + +@allowed([ + '3.2' + '3.6' + '4.0' + '4.2' + '5.0' + '6.0' + '7.0' + '8.0' +]) + +@description('Specifies the Cosmos DB server version to use.') +param serverVersion string = '7.0' + +@minValue(10) +@maxValue(2147483647) +@description('Specifies the max stale requests. Required for BoundedStaleness. Valid ranges, Single Region: 10 to 2147483647. Multi Region: 100000 to 2147483647.') +param maxStalenessPrefix int = 100000 + +@minValue(5) +@maxValue(86400) +@description('Specifies the max lag time (seconds). Required for BoundedStaleness. Valid ranges, Single Region: 5 to 84600. Multi Region: 300 to 86400.') +param maxIntervalInSeconds int = 300 + +@description('Specifies the resource id of the Log Analytics workspace.') +param workspaceId string + +@description('Specifies the tags to be applied to the resources.') +param tags object = {} + + +//******************************************** +// Variables +//******************************************** +var consistencyPolicy = { + Eventual: { + defaultConsistencyLevel: 'Eventual' + } + ConsistentPrefix: { + defaultConsistencyLevel: 'ConsistentPrefix' + } + Session: { + defaultConsistencyLevel: 'Session' + } + BoundedStaleness: { + defaultConsistencyLevel: 'BoundedStaleness' + maxStalenessPrefix: maxStalenessPrefix + maxIntervalInSeconds: maxIntervalInSeconds + } + Strong: { + defaultConsistencyLevel: 'Strong' + } +} +var locations = [ + { + locationName: primaryRegion + failoverPriority: 0 + isZoneRedundant: false + } + { + locationName: secondaryRegion + failoverPriority: 1 + isZoneRedundant: false + } +] +var diagnosticSettingsName = 'default' +var logCategories = [ + 'DataPlaneRequests' + 'MongoRequests' +] +var metricCategories = [ + 'Requests' +] +var logs = [for category in logCategories: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: 0 + } +}] +var metrics = [for category in metricCategories: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: 0 + } +}] + +//******************************************** +// Resources +//******************************************** +resource account 'Microsoft.DocumentDB/databaseAccounts@2025-04-15' = { + name: toLower(name) + location: location + kind: 'MongoDB' + tags: tags + properties: { + consistencyPolicy: consistencyPolicy[defaultConsistencyLevel] + locations: locations + databaseAccountOfferType: 'Standard' + enableAutomaticFailover: true + apiProperties: { + serverVersion: serverVersion + } + capabilities: [ + { + name: 'DisableRateLimitingResponses' + } + ] + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15' = { + parent: account + name: databaseName + tags: tags + properties: { + resource: { + id: databaseName + } + options: { + throughput: sharedThroughput + } + } +} + +resource collection 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15' = { + parent: database + name: collectionName + tags: tags + properties: { + resource: { + id: collectionName + shardKey: { + username: 'Hash' + } + // Use a for loop to dynamically create the 'indexes' array based on the 'mongoDbIndexKeys' parameter + indexes: [for key in mongoDbIndexKeys: { + key: { + keys: [ + key + ] + } + }] + } + options: { + throughput: dedicatedThroughput + } + } +} + +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: diagnosticSettingsName + scope: account + properties: { + workspaceId: workspaceId + logs: logs + metrics: metrics + } +} + +//******************************************** +// Outputs +//******************************************** +output id string = account.id +output name string = account.name +output documentEndpoint string = account.properties.documentEndpoint +output databaseId string = database.id +output databaseName string = database.name +output collectionId string = collection.id +output collectionName string = collection.name diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/private-dns-zone.bicep b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/private-dns-zone.bicep new file mode 100644 index 0000000..d849259 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/private-dns-zone.bicep @@ -0,0 +1,41 @@ +//******************************************** +// Parameters +//******************************************** +@description('Specifies the name of the private DNS zone.') +param name string + +@description('Specifies the resource ID of the virtual network where private endpoints will be created.') +param vnetId string + +@description('Specifies the resource tags.') +param tags object + +//******************************************** +// Resources +//******************************************** + +// Private DNS Zones +resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = { + name: name + location: 'global' + tags: tags +} + +// Virtual Network Links +resource privateDnsZoneVirtualNetworkLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = { + parent: privateDnsZone + name: 'link-to-vnet' + location: 'global' + properties: { + registrationEnabled: false + virtualNetwork: { + id: vnetId + } + } +} + +//******************************************** +// Outputs +//******************************************** +output id string = privateDnsZone.id +output name string = privateDnsZone.name diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/private-endpoint.bicep b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/private-endpoint.bicep new file mode 100644 index 0000000..8fd35b8 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/private-endpoint.bicep @@ -0,0 +1,72 @@ +//******************************************** +// Parameters +//******************************************** +@description('Specifies the name of the private endpoint.') +param name string + +@description('Specifies the location.') +param location string = resourceGroup().location + +@description('Specifies the resource ID of the virtual network where private endpoints will be created.') +param vnetId string + +@description('Specifies the resource ID of the subnet where private endpoints will be created.') +param subnetId string + +@description('Specifies the group IDs for the private link service connection.') +param groupIds array + +@description('Specifies the resource ID of the target resource.') +param privateLinkServiceId string + +@description('Specifies the resource ID of the private DNS zone.') +param privateDnsZoneId string + +@description('Specifies the resource tags.') +param tags object + +//******************************************** +// Resources +//******************************************** + +// Private Endpoints +resource privateEndpoint 'Microsoft.Network/privateEndpoints@2025-05-01' = { + name: name + location: location + tags: tags + properties: { + privateLinkServiceConnections: [ + { + name: '${name}-pls-connection' + properties: { + privateLinkServiceId: privateLinkServiceId + groupIds: groupIds + } + } + ] + subnet: { + id: subnetId + } + } +} + +resource privateDnsZoneGroupName 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2025-05-01' = { + parent: privateEndpoint + name: 'private-dns-zone-group' + properties: { + privateDnsZoneConfigs: [ + { + name: 'dnsConfig' + properties: { + privateDnsZoneId: privateDnsZoneId + } + } + ] + } +} + +//******************************************** +// Outputs +//******************************************** +output id string = privateEndpoint.id +output name string = privateEndpoint.name diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/virtual-network.bicep b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/virtual-network.bicep new file mode 100644 index 0000000..1c7a088 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/virtual-network.bicep @@ -0,0 +1,239 @@ +//******************************************** +// Parameters +//******************************************** +@description('Specifies the name of the virtual network.') +param virtualNetworkName string + +@description('Specifies the location.') +param location string = resourceGroup().location + +@description('Specifies the address prefixes of the virtual network.') +param virtualNetworkAddressPrefixes string = '10.0.0.0/8' + +@description('Specifies the name of the subnet used by the Web App for the regional virtual network integration.') +param webAppSubnetName string = 'functionAppSubnet' + +@description('Specifies the address prefix of the subnet used by the Web App for the regional virtual network integration.') +param webAppSubnetAddressPrefix string = '10.0.0.0/24' + +@description('Specifies the name of the network security group associated to the subnet hosting the Web App.') +param webAppSubnetNsgName string = '' + +@description('Specifies the name of the subnet which contains the private endpoint to the Azure CosmosDB for MongoDB API account.') +param peSubnetName string = 'pe-subnet' + +@description('Specifies the address prefix of the subnet which contains the private endpoint to the Azure CosmosDB for MongoDB API account.') +param peSubnetAddressPrefix string = '10.0.1.0/24' + +@description('Specifies the name of the network security group associated to the subnet hosting the private endpoint to the Azure CosmosDB for MongoDB API account.') +param peSubnetNsgName string = '' + +@description('Specifies the name of the Azure NAT Gateway.') +param natGatewayName string + +@description('Specifies a list of availability zones denoting the zone in which Nat Gateway should be deployed.') +param natGatewayZones array = [] + +@description('Specifies the name of the public IP prefix for the Azure NAT Gateway.') +param natGatewayPublicIpPrefixName string + +@description('Specifies the length of the Public IP Prefix.') +@minValue(28) +@maxValue(32) +param natGatewayPublicIpPrefixLength int = 31 + +@description('Specifies the idle timeout in minutes for the Azure NAT Gateway.') +param natGatewayIdleTimeoutMins int = 30 + +@description('Specifies the delegation service name.') +param delegationServiceName string + +@description('Specifies the resource id of the Log Analytics workspace.') +param workspaceId string + +@description('Specifies the resource tags.') +param tags object + +//******************************************** +// Variables +//******************************************** +var diagnosticSettingsName = 'default' +var nsgLogCategories = [ + 'NetworkSecurityGroupEvent' + 'NetworkSecurityGroupRuleCounter' +] +var nsgLogs = [for category in nsgLogCategories: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: 0 + } +}] +var vnetLogCategories = [ + 'VMProtectionAlerts' +] +var vnetMetricCategories = [ + 'AllMetrics' +] +var vnetLogs = [for category in vnetLogCategories: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: 0 + } +}] +var vnetMetrics = [for category in vnetMetricCategories: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: 0 + } +}] + +//******************************************** +// Resources +//******************************************** + +// Virtual Network +resource vnet 'Microsoft.Network/virtualNetworks@2024-03-01' = { + name: virtualNetworkName + location: location + tags: tags + properties: { + addressSpace: { + addressPrefixes: [ + virtualNetworkAddressPrefixes + ] + } + subnets: [ + { + name: webAppSubnetName + properties: { + addressPrefix: webAppSubnetAddressPrefix + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Disabled' + networkSecurityGroup: { + id: webAppSubnetNsg.id + } + natGateway: { + id: natGateway.id + } + delegations: [ + { + name: 'delegation' + properties: { + serviceName: delegationServiceName + } + } + ] + } + } + { + name: peSubnetName + properties: { + addressPrefix: peSubnetAddressPrefix + networkSecurityGroup: { + id: peSubnetNsg.id + } + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Disabled' + natGateway: { + id: natGateway.id + } + } + } + ] + } +} + +resource webAppSubnetNsg 'Microsoft.Network/networkSecurityGroups@2025-05-01' = { + name: webAppSubnetNsgName + location: location + tags: tags + properties: { + securityRules: [ + ] + } +} + +resource peSubnetNsg 'Microsoft.Network/networkSecurityGroups@2025-05-01' = { + name: peSubnetNsgName + location: location + tags: tags + properties: { + securityRules: [ + ] + } +} + +// NAT Gateway +resource natGatewayPublicIpPrefix 'Microsoft.Network/publicIPPrefixes@2025-05-01' = { + name: natGatewayPublicIpPrefixName + location: location + sku: { + name: 'Standard' + } + zones: !empty(natGatewayZones) ? natGatewayZones : [] + properties: { + publicIPAddressVersion: 'IPv4' + prefixLength: natGatewayPublicIpPrefixLength + } +} + +resource natGateway 'Microsoft.Network/natGateways@2025-05-01' = { + name: natGatewayName + location: location + sku: { + name: 'Standard' + } + zones: !empty(natGatewayZones) ? natGatewayZones : [] + properties: { + publicIpPrefixes: [ + { + id: natGatewayPublicIpPrefix.id + } + ] + idleTimeoutInMinutes: natGatewayIdleTimeoutMins + } +} + +resource peSubnetNsgDiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: diagnosticSettingsName + scope: peSubnetNsg + properties: { + workspaceId: workspaceId + logs: nsgLogs + } +} + +resource webAppSubnetNsgDiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: diagnosticSettingsName + scope: webAppSubnetNsg + properties: { + workspaceId: workspaceId + logs: nsgLogs + } +} + +resource vnetDiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { + name: diagnosticSettingsName + scope: vnet + properties: { + workspaceId: workspaceId + logs: vnetLogs + metrics: vnetMetrics + } +} + +//******************************************** +// Outputs +//******************************************** +output virtualNetworkId string = vnet.id +output virtualNetworkName string = vnet.name +output webAppSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, webAppSubnetName) +output webAppSubnetName string = webAppSubnetName +output peSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, peSubnetName) +output peSubnetName string = peSubnetName diff --git a/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/web-app.bicep b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/web-app.bicep new file mode 100644 index 0000000..8af4bec --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/bicep/modules/web-app.bicep @@ -0,0 +1,212 @@ +//******************************************** +// Parameters +//******************************************** + +@description('Specifies a globally unique name the Azure Web App.') +param name string + +@description('Specifies the location.') +param location string = resourceGroup().location + +@description('Specifies the kind of the hosting plan.') +@allowed([ + 'app' // Windows Web app + 'app,linux' // Linux Web app + 'app,linux,container' // Linux Container Web app + 'hyperV' // Windows Container Web App + 'app,container,windows' // Windows Container Web App + 'app,linux,kubernetes' // Linux Web App on ARC + 'app,linux,container,kubernetes' // Linux Container Web App on ARC + 'functionapp' // Function Code App + 'functionapp,linux' // Linux Consumption Function app + 'functionapp,linux,container,kubernetes' // Function Container App on ARC + 'functionapp,linux,kubernetes' // Function Code App on ARC +]) +param kind string = 'app,linux' + +@description('Specifies the language runtime used by the Azure Web App.') +@allowed([ + 'dotnet' + 'dotnet-isolated' + 'python' + 'java' + 'node' + 'powerShell' + 'custom' +]) +param runtimeName string + +@description('Specifies the target language version used by the Azure Web App.') +param runtimeVersion string + +@description('Specifies the minimum TLS version for the Azure Web App.') +@allowed([ + '1.2' + '1.3' +]) +param minTlsVersion string = '1.2' + +@description('Specifies whether the public network access is enabled or disabled') +@allowed([ + 'Enabled' + 'Disabled' +]) +param publicNetworkAccess string = 'Enabled' + +@description('Specifies whether HTTPS is enforced for the Azure Web App.') +param httpsOnly bool = true + +@description('Specifies the name of the hosting plan.') +param hostingPlanName string + +@description('Specifies the name of the Azure Cosmos DB account.') +param accountName string + +@description('Specifies the name of the virtual network.') +param virtualNetworkName string + +@description('Specifies the name of the subnet used by Azure Functions for the regional virtual network integration.') +param subnetName string + +@description('Specifies the resource id of the Log Analytics workspace.') +param workspaceId string + +@description('Specifies the name for the Mongo DB database.') +param databaseName string = 'sampledb' + +@description('Specifies the name for the Mongo DB collection.') +param collectionName string = 'activities' + +@description('Specifies the username for the application.') +param username string = 'paolo' + +@description('Specifies the optional Git Repo URL.') +param repoUrl string = ' ' + +@description('Specifies the resource tags.') +param tags object + +//******************************************** +// Variables +//******************************************** + +// Generates a unique container name for deployments. +var diagnosticSettingsName = 'default' +var logCategories = [ + 'AppServiceHTTPLogs' + 'AppServiceConsoleLogs' + 'AppServiceAppLogs' + 'AppServiceAuditLogs' + 'AppServiceIPSecAuditLogs' + 'AppServicePlatformLogs' + 'AppServiceAuthenticationLogs' +] +var metricCategories = [ + 'AllMetrics' +] +var logs = [ + for category in logCategories: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: 0 + } + } +] +var metrics = [ + for category in metricCategories: { + category: category + enabled: true + retentionPolicy: { + enabled: true + days: 0 + } + } +] + +//******************************************** +// Resources +//******************************************** + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' existing = { + name: virtualNetworkName +} + +resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-05-01' existing = { + parent: virtualNetwork + name: subnetName +} + +resource hostingPlan 'Microsoft.Web/serverfarms@2024-04-01' existing = { + name: hostingPlanName +} + +resource account 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = { + name: toLower(accountName) +} + +resource webApp 'Microsoft.Web/sites@2025-03-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + httpsOnly: httpsOnly + serverFarmId: hostingPlan.id + virtualNetworkSubnetId: subnet.id + outboundVnetRouting: { + allTraffic: true + } + siteConfig: { + linuxFxVersion: toUpper('${runtimeName}|${runtimeVersion}') + minTlsVersion: minTlsVersion + publicNetworkAccess: publicNetworkAccess + } + } + identity: { + type: 'SystemAssigned' + } +} + + +resource configAppSettings 'Microsoft.Web/sites/config@2024-11-01' = { + parent: webApp + name: 'appsettings' + properties: { + SCM_DO_BUILD_DURING_DEPLOYMENT: 'true' + ENABLE_ORYX_BUILD: 'true' + COSMOSDB_CONNECTION_STRING: account.listConnectionStrings().connectionStrings[0].connectionString + COSMOSDB_DATABASE_NAME: databaseName + COSMOSDB_COLLECTION_NAME: collectionName + WEBSITE_PORT: '8000' + LOGIN_NAME: username + } +} + +resource webAppSourceControl 'Microsoft.Web/sites/sourcecontrols@2024-11-01' = if (contains(repoUrl,'http')){ + name: 'web' + parent: webApp + properties: { + repoUrl: repoUrl + branch: 'master' + isManualIntegration: true + } +} + +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if(!empty(workspaceId)) { + name: diagnosticSettingsName + scope: webApp + properties: { + workspaceId: workspaceId + logs: logs + metrics: metrics + } +} + +//******************************************** +// Outputs +//******************************************** +output id string = webApp.id +output name string = webApp.name +output defaultHostName string = webApp.properties.defaultHostName diff --git a/samples/web-app-cosmosdb-mongodb-api/python/images/architecture.png b/samples/web-app-cosmosdb-mongodb-api/python/images/architecture.png index 835917f..ef6e11d 100644 Binary files a/samples/web-app-cosmosdb-mongodb-api/python/images/architecture.png and b/samples/web-app-cosmosdb-mongodb-api/python/images/architecture.png differ diff --git a/samples/web-app-cosmosdb-mongodb-api/python/images/mongodb-compass.png b/samples/web-app-cosmosdb-mongodb-api/python/images/mongodb-compass.png deleted file mode 100644 index 3ab7ae6..0000000 Binary files a/samples/web-app-cosmosdb-mongodb-api/python/images/mongodb-compass.png and /dev/null differ diff --git a/samples/web-app-cosmosdb-mongodb-api/python/images/vacation-planner.png b/samples/web-app-cosmosdb-mongodb-api/python/images/vacation-planner.png index b4d04b3..7fbb14f 100644 Binary files a/samples/web-app-cosmosdb-mongodb-api/python/images/vacation-planner.png and b/samples/web-app-cosmosdb-mongodb-api/python/images/vacation-planner.png differ diff --git a/samples/web-app-cosmosdb-mongodb-api/python/scripts/README.md b/samples/web-app-cosmosdb-mongodb-api/python/scripts/README.md index 84e092f..9b6a6b1 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/scripts/README.md +++ b/samples/web-app-cosmosdb-mongodb-api/python/scripts/README.md @@ -28,15 +28,23 @@ For more information, see [Get started with the az tool on LocalStack](https://a This [deploy.sh](deploy.sh) script creates the following Azure resources using Azure CLI commands: -1. [Azure Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli): Logical container for all gaming system resources. -2. [Azure CosmosDB Account (MongoDB API)](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/introduction): A globally distributed database account configured for MongoDB workloads, with multi-region failover. -3. [MongoDB Database](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `sampledb` database for storing application data. -4. [MongoDB Collection](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `activities` collection within `sampledb` for storing vacation activity records. -5. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans): The compute resource that hosts the web application. -6. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview): Hosts the Python Flask single-page application (*Vacation Planner*), connected to CosmosDB for MongoDB. -7. [App Service Source Control](https://learn.microsoft.com/en-us/rest/api/appservice/web-apps/create-or-update-source-control?view=rest-appservice-2024-11-01): (Optional) Configures automatic deployment from a public GitHub repository. - -The web app allows users to plan and manage vacation activities, storing all activity data in a MongoDB collection. For more information on the sample application, see [Azure Web App with Azure CosmosDB for MongoDB](../README.md). +1. [Azure Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli): A logical container scoping all resources in this sample. +2. [Azure Virtual Network](https://learn.microsoft.com/azure/virtual-network/virtual-networks-overview): Hosts two subnets: + - *app-subnet*: Dedicated to [regional VNet integration](https://learn.microsoft.com/azure/azure-functions/functions-networking-options?tabs=azure-portal#outbound-networking-features) with the Function App. + - *pe-subnet*: Used for hosting Azure Private Endpoints. +3. [Azure Private DNS Zone](https://learn.microsoft.com/azure/dns/private-dns-privatednszone): Handles DNS resolution for the CosmosDB for MongoDB Private Endpoint within the virtual network. +4. [Azure Private Endpoint](https://learn.microsoft.com/azure/private-link/private-endpoint-overview): Secures network access to the CosmosDB for MongoDB account via a private IP within the VNet. +5. [Azure NAT Gateway](https://learn.microsoft.com/azure/nat-gateway/nat-overview): Provides deterministic outbound connectivity for the Web App. Included for completeness; the sample app does not call any external services. +6. [Azure Network Security Group](https://learn.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview): Enforces inbound and outbound traffic rules across the virtual network's subnets. +7. [Azure Log Analytics Workspace](https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-overview): Centralizes diagnostic logs and metrics from all resources in the solution. +8. [Azure Cosmos DB for MongoDB](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/introduction): A globally distributed database account optimized for MongoDB workloads, with multi-region failover enabled. +9. [MongoDB Database](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `sampledb` database that holds all application data. +10. [MongoDB Collection](https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/overview): The `activities` collection within `sampledb`, used to store vacation activity records. +11. [Azure App Service Plan](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans): The underlying compute tier that hosts the web application. +12. [Azure Web App](https://learn.microsoft.com/en-us/azure/app-service/overview): Runs the Python Flask single-page application (*Vacation Planner*), connected to CosmosDB for MongoDB via VNet integration. +13. [App Service Source Control](https://learn.microsoft.com/en-us/rest/api/appservice/web-apps/create-or-update-source-control?view=rest-appservice-2024-11-01): *(Optional)* Configures continuous deployment from a public GitHub repository. + +The web app enables users to plan and manage vacation activities, with all data persisted in a CosmosDB-backed MongoDB collection. For more information on the sample application, see [Azure Web App with Azure CosmosDB for MongoDB](../README.md). ## Provisioning Scripts @@ -89,12 +97,27 @@ Run the deployment script: ## Validation -After deployment, you can use the `validate.sh` script to verify that all resources were created and configured correctly: +Once the deployment completes, run the [validate.sh](../scripts/validate.sh) script to confirm that all resources were provisioned and configured as expected: ```bash #!/bin/bash # Variables +PREFIX='local' +SUFFIX='test' +RESOURCE_GROUP_NAME="${PREFIX}-rg" +LOG_ANALYTICS_NAME="${PREFIX}-log-analytics-${SUFFIX}" +WEBAPP_SUBNET_NSG_NAME="${PREFIX}-webapp-subnet-nsg-${SUFFIX}" +PE_SUBNET_NSG_NAME="${PREFIX}-pe-subnet-nsg-${SUFFIX}" +NAT_GATEWAY_NAME="${PREFIX}-nat-gateway-${SUFFIX}" +VIRTUAL_NETWORK_NAME="${PREFIX}-vnet-${SUFFIX}" +PRIVATE_DNS_ZONE_NAME="privatelink.mongo.cosmos.azure.com" +PRIVATE_ENDPOINT_NAME="${PREFIX}-mongodb-pe-${SUFFIX}" +APP_SERVICE_PLAN_NAME="${PREFIX}-app-service-plan-${SUFFIX}" +WEBAPP_NAME="${PREFIX}-webapp-${SUFFIX}" +COSMOSDB_ACCOUNT_NAME="${PREFIX}-mongodb-${SUFFIX}" +MONGODB_DATABASE_NAME="sampledb" +COLLECTION_NAME="activities" ENVIRONMENT=$(az account show --query environmentName --output tsv) # Choose the appropriate CLI based on the environment @@ -107,41 +130,121 @@ else fi # Check resource group +echo -e "[$RESOURCE_GROUP_NAME] resource group:\n" $AZ group show \ ---name local-rg \ ---output table - -# List resources -$AZ resource list \ ---resource-group local-rg \ ---output table + --name "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check App Service Plan +echo -e "\n[$APP_SERVICE_PLAN_NAME] app service plan:\n" +$AZ appservice plan show \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --name "$APP_SERVICE_PLAN_NAME" \ + --output table \ + --only-show-errors # Check Azure Web App +echo -e "\n[$WEBAPP_NAME] web app:\n" $AZ webapp show \ ---name local-webapp-test \ ---resource-group local-rg \ ---output table + --name "$WEBAPP_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors # Check Azure CosmosDB account +echo -e "\n[$COSMOSDB_ACCOUNT_NAME] cosmosdb account:\n" $AZ cosmosdb show \ ---name local-mongodb-test \ ---resource-group local-rg \ ---output table + --name "$COSMOSDB_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query '{Name:name,Location:location,ResourceGroup:resourceGroup,DocumentEndpoint:documentEndpoint}' \ + --output table \ + --only-show-errors # Check MongoDB database +echo -e "\n[$MONGODB_DATABASE_NAME] mongodb database:\n" $AZ cosmosdb mongodb database show \ ---name sampledb \ ---account-name local-mongodb-test \ ---resource-group local-rg \ ---output table + --name "$MONGODB_DATABASE_NAME" \ + --account-name "$COSMOSDB_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query '{Name:name,ResourceGroup:resourceGroup}' \ + --output table \ + --only-show-errors # Check MongoDB collection +echo -e "\n[$COLLECTION_NAME] mongodb collection:\n" $AZ cosmosdb mongodb collection show \ ---name activities \ ---database-name sampledb \ ---account-name local-mongodb-test \ ---resource-group local-rg \ ---output table + --name "$COLLECTION_NAME" \ + --database-name "$MONGODB_DATABASE_NAME" \ + --account-name "$COSMOSDB_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Log Analytics Workspace +echo -e "\n[$LOG_ANALYTICS_NAME] log analytics workspace:\n" +$AZ monitor log-analytics workspace show \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --workspace-name "$LOG_ANALYTICS_NAME" \ + --query '{Name:name,Location:location,ResourceGroup:resourceGroup}' \ + --output table \ + --only-show-errors + +# Check NAT Gateway +echo -e "\n[$NAT_GATEWAY_NAME] nat gateway:\n" +$AZ network nat gateway show \ + --name "$NAT_GATEWAY_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Virtual Network +echo -e "\n[$VIRTUAL_NETWORK_NAME] virtual network:\n" +$AZ network vnet show \ + --name "$VIRTUAL_NETWORK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Private DNS Zone +echo -e "\n[$PRIVATE_DNS_ZONE_NAME] private dns zone:\n" +$AZ network private-dns zone show \ + --name "$PRIVATE_DNS_ZONE_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query '{Name:name,ResourceGroup:resourceGroup,RecordSets:recordSets,VirtualNetworkLinks:virtualNetworkLinks}' \ + --output table \ + --only-show-errors + +# Check Private Endpoint +echo -e "\n[$PRIVATE_ENDPOINT_NAME] private endpoint:\n" +$AZ network private-endpoint show \ + --name "$PRIVATE_ENDPOINT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Web App Subnet NSG +echo -e "\n[$WEBAPP_SUBNET_NSG_NAME] network security group:\n" +$AZ network nsg show \ + --name "$WEBAPP_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Private Endpoint Subnet NSG +echo -e "\n[$PE_SUBNET_NSG_NAME] network security group:\n" +$AZ network nsg show \ + --name "$PE_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# List resources +echo -e "\n[$RESOURCE_GROUP_NAME] all resources:\n" +$AZ resource list \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors ``` ## Cleanup diff --git a/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh b/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh index 923dedc..0a4b968 100755 --- a/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh +++ b/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh @@ -5,9 +5,26 @@ PREFIX='local' SUFFIX='test' LOCATION='westeurope' RESOURCE_GROUP_NAME="${PREFIX}-rg" +LOG_ANALYTICS_NAME="${PREFIX}-log-analytics-${SUFFIX}" +DIAGNOSTIC_SETTINGS_NAME='default' +WEBAPP_SUBNET_NSG_NAME="${PREFIX}-webapp-subnet-nsg-${SUFFIX}" +PE_SUBNET_NSG_NAME="${PREFIX}-pe-subnet-nsg-${SUFFIX}" +NAT_GATEWAY_NAME="${PREFIX}-nat-gateway-${SUFFIX}" +PIP_PREFIX_NAME="${PREFIX}-nat-gateway-pip-prefix-${SUFFIX}" +VIRTUAL_NETWORK_NAME="${PREFIX}-vnet-${SUFFIX}" +VIRTUAL_NETWORK_ADDRESS_PREFIX="10.0.0.0/8" +WEBAPP_SUBNET_NAME="app-subnet" +WEBAPP_SUBNET_PREFIX="10.0.0.0/24" +PE_SUBNET_NAME="pe-subnet" +PE_SUBNET_PREFIX="10.0.1.0/24" +VIRTUAL_NETWORK_LINK_NAME="link-to-vnet" +PRIVATE_DNS_ZONE_NAME="privatelink.mongo.cosmos.azure.com" +PRIVATE_ENDPOINT_NAME="${PREFIX}-mongodb-pe-${SUFFIX}" +PRIVATE_ENDPOINT_GROUP="mongodb" +PRIVATE_DNS_ZONE_GROUP_NAME="default" APP_SERVICE_PLAN_NAME="${PREFIX}-app-service-plan-${SUFFIX}" APP_SERVICE_PLAN_SKU="S1" -WEB_APP_NAME="${PREFIX}-webapp-${SUFFIX}" +WEBAPP_NAME="${PREFIX}-webapp-${SUFFIX}" COSMOSDB_ACCOUNT_NAME="${PREFIX}-mongodb-${SUFFIX}" MONGODB_API_VERSION="7.0" MONGODB_DATABASE_NAME="sampledb" @@ -16,10 +33,11 @@ INDEXES='[{"key":{"keys":["_id"]}},{"key":{"keys":["username"]}},{"key":{"keys": SHARD="username" THROUGHPUT=400 RUNTIME="python" -RUNTIME_VERSION="3.12" +RUNTIME_VERSION="3.13" LOGIN_NAME="paolo" CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" ZIPFILE="planner_website.zip" +SUBSCRIPTION_ID=$(az account show --query id --output tsv) ENVIRONMENT=$(az account show --query environmentName --output tsv) # Change the current directory to the script's directory @@ -48,25 +66,55 @@ else exit 1 fi -# Create a CosmosDB account with MongoDB kind -echo "Creating [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group..." -$AZ cosmosdb create \ +# Check if the CosmosDB account already exists +echo "Checking if [$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group..." +$AZ cosmosdb show \ --name $COSMOSDB_ACCOUNT_NAME \ --resource-group $RESOURCE_GROUP_NAME \ - --locations regionName=$LOCATION \ - --kind MongoDB \ - --server-version $MONGODB_API_VERSION \ - --default-consistency-level Session \ - --only-show-errors 1>/dev/null + --only-show-errors &>/dev/null -if [ $? -eq 0 ]; then - echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account successfully created in the [$RESOURCE_GROUP_NAME] resource group" +if [[ $? != 0 ]]; then + echo "No [$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" + echo "Creating [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group..." + + # Create a CosmosDB account with MongoDB kind + $AZ cosmosdb create \ + --name $COSMOSDB_ACCOUNT_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --locations regionName=$LOCATION \ + --kind MongoDB \ + --server-version $MONGODB_API_VERSION \ + --default-consistency-level Session \ + --only-show-errors 1>/dev/null + + if [ $? -eq 0 ]; then + echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account successfully created in the [$RESOURCE_GROUP_NAME] resource group" + else + echo "Failed to create [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group" + exit 1 + fi +else + echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" +fi + +# Retrieve account resource id +echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account resource id in the [$RESOURCE_GROUP_NAME] resource group..." +COSMOSDB_ACCOUNT_ID=$($AZ cosmosdb show \ + --name $COSMOSDB_ACCOUNT_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --query id \ + --output tsv \ + --only-show-errors) + +if [ -n "$COSMOSDB_ACCOUNT_ID" ]; then + echo "CosmosDB account resource id retrieved successfully: $COSMOSDB_ACCOUNT_ID" else - echo "Failed to create [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group" + echo "Failed to retrieve CosmosDB account resource id." exit 1 fi # Retrieve document endpoint +echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account document endpoint in the [$RESOURCE_GROUP_NAME] resource group..." DOCUMENT_ENDPOINT=$($AZ cosmosdb show \ --name $COSMOSDB_ACCOUNT_NAME \ --resource-group $RESOURCE_GROUP_NAME \ @@ -81,39 +129,68 @@ else exit 1 fi -# Create MongoDB database -echo "Creating [$MONGODB_DATABASE_NAME] MongoDB database in the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account..." -$AZ cosmosdb mongodb database create \ +# Check if the MongoDB database already exists +echo "Checking if [$MONGODB_DATABASE_NAME] MongoDB database already exists in the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account..." +$AZ cosmosdb mongodb database show \ --account-name $COSMOSDB_ACCOUNT_NAME \ --name $MONGODB_DATABASE_NAME \ --resource-group $RESOURCE_GROUP_NAME \ - --output json \ - --only-show-errors 1>/dev/null + --only-show-errors &>/dev/null -if [ $? -eq 0 ]; then - echo "[$MONGODB_DATABASE_NAME] MongoDB database successfully created in the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account" +if [[ $? != 0 ]]; then + echo "No [$MONGODB_DATABASE_NAME] MongoDB database already exists in the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account" + echo "Creating [$MONGODB_DATABASE_NAME] MongoDB database in the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account..." + + # Create MongoDB database in the CosmosDB account + $AZ cosmosdb mongodb database create \ + --account-name $COSMOSDB_ACCOUNT_NAME \ + --name $MONGODB_DATABASE_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --output json \ + --only-show-errors 1>/dev/null + + if [ $? -eq 0 ]; then + echo "[$MONGODB_DATABASE_NAME] MongoDB database successfully created in the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account" + else + echo "Failed to create [$MONGODB_DATABASE_NAME] MongoDB database in the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account" + exit 1 + fi else - echo "Failed to create [$MONGODB_DATABASE_NAME] MongoDB database in the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account" - exit 1 + echo "[$MONGODB_DATABASE_NAME] MongoDB database already exists in the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account" fi -# Create a MongoDB database collection -echo "Creating [$COLLECTION_NAME] collection in the [$MONGODB_DATABASE_NAME] MongoDB database..." -$AZ cosmosdb mongodb collection create \ +# Check if the MongoDB database collection already exists +echo "Checking if [$COLLECTION_NAME] collection already exists in the [$MONGODB_DATABASE_NAME] MongoDB database..." +$AZ cosmosdb mongodb collection show \ --account-name $COSMOSDB_ACCOUNT_NAME \ - --resource-group $RESOURCE_GROUP_NAME \ --database-name $MONGODB_DATABASE_NAME \ --name $COLLECTION_NAME \ - --idx "$INDEXES" \ - --shard $SHARD \ - --throughput $THROUGHPUT \ - --only-show-errors 1>/dev/null + --resource-group $RESOURCE_GROUP_NAME \ + --only-show-errors &>/dev/null -if [ $? -eq 0 ]; then - echo "[$COLLECTION_NAME] collection successfully created in the [$MONGODB_DATABASE_NAME] MongoDB database" +if [[ $? != 0 ]]; then + echo "No [$COLLECTION_NAME] collection already exists in the [$MONGODB_DATABASE_NAME] MongoDB database" + echo "Creating [$COLLECTION_NAME] collection in the [$MONGODB_DATABASE_NAME] MongoDB database..." + + # Create a MongoDB database collection + $AZ cosmosdb mongodb collection create \ + --account-name $COSMOSDB_ACCOUNT_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --database-name $MONGODB_DATABASE_NAME \ + --name $COLLECTION_NAME \ + --idx "$INDEXES" \ + --shard $SHARD \ + --throughput $THROUGHPUT \ + --only-show-errors 1>/dev/null + + if [ $? -eq 0 ]; then + echo "[$COLLECTION_NAME] collection successfully created in the [$MONGODB_DATABASE_NAME] MongoDB database" + else + echo "Failed to create [$COLLECTION_NAME] collection in the [$MONGODB_DATABASE_NAME] MongoDB database" + exit 1 + fi else - echo "Failed to create [$COLLECTION_NAME] collection in the [$MONGODB_DATABASE_NAME] MongoDB database" - exit 1 + echo "[$COLLECTION_NAME] collection already exists in the [$MONGODB_DATABASE_NAME] MongoDB database" fi # List CosmosDB connection strings @@ -132,8 +209,380 @@ else echo "Failed to retrieve CosmosDB connection strings." fi -# Create App Service Plan -echo "Creating App Service Plan [$APP_SERVICE_PLAN_NAME]..." +# Check if the network security group for the web app subnet already exists +echo "Checking if [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet actually exists in the [$RESOURCE_GROUP_NAME] resource group..." +$AZ network nsg show \ + --name "$WEBAPP_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet actually exists in the [$RESOURCE_GROUP_NAME] resource group" + echo "Creating [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet..." + + # Create the network security group for the web app subnet + $AZ network nsg create \ + --name "$WEBAPP_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --location "$LOCATION" \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet successfully created in the [$RESOURCE_GROUP_NAME] resource group" + else + echo "Failed to create [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet in the [$RESOURCE_GROUP_NAME] resource group" + exit 1 + fi +else + echo "[$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet already exists in the [$RESOURCE_GROUP_NAME] resource group" +fi + +# Get the resource id of the network security group for the web app subnet +echo "Getting [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet resource id in the [$RESOURCE_GROUP_NAME] resource group..." +WEBAPP_SUBNET_NSG_ID=$($AZ network nsg show \ + --name "$WEBAPP_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query id \ + --output tsv \ + --only-show-errors) + +if [[ -n $WEBAPP_SUBNET_NSG_ID ]]; then + echo "[$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet resource id retrieved successfully: $WEBAPP_SUBNET_NSG_ID" +else + echo "Failed to retrieve [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet resource id in the [$RESOURCE_GROUP_NAME] resource group" + exit 1 +fi + +# Check if the network security group for the private endpoint subnet already exists +echo "Checking if [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet actually exists in the [$RESOURCE_GROUP_NAME] resource group..." +$AZ network nsg show \ + --name "$PE_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet actually exists in the [$RESOURCE_GROUP_NAME] resource group" + echo "Creating [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet..." + + # Create the network security group for the private endpoint subnet + $AZ network nsg create \ + --name "$PE_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --location "$LOCATION" \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet successfully created in the [$RESOURCE_GROUP_NAME] resource group" + else + echo "Failed to create [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet in the [$RESOURCE_GROUP_NAME] resource group" + exit 1 + fi +else + echo "[$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet already exists in the [$RESOURCE_GROUP_NAME] resource group" +fi + +# Get the resource id of the network security group for the private endpoint subnet +echo "Getting [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet resource id in the [$RESOURCE_GROUP_NAME] resource group..." +PE_SUBNET_NSG_ID=$($AZ network nsg show \ + --name "$PE_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query id \ + --output tsv \ + --only-show-errors) + +if [[ -n $PE_SUBNET_NSG_ID ]]; then + echo "[$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet resource id retrieved successfully: $PE_SUBNET_NSG_ID" +else + echo "Failed to retrieve [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet resource id in the [$RESOURCE_GROUP_NAME] resource group" + exit 1 +fi + +# Check if the public IP prefix for the NAT Gateway already exists +echo "Checking if [$PIP_PREFIX_NAME] public IP prefix for the NAT Gateway actually exists in the [$RESOURCE_GROUP_NAME] resource group..." +$AZ network public-ip prefix show \ + --name "$PIP_PREFIX_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$PIP_PREFIX_NAME] public IP prefix for the NAT Gateway actually exists in the [$RESOURCE_GROUP_NAME] resource group" + echo "Creating [$PIP_PREFIX_NAME] public IP prefix for the NAT Gateway in the [$RESOURCE_GROUP_NAME] resource group..." + + # Create the public IP prefix for the NAT Gateway + $AZ network public-ip prefix create \ + --name "$PIP_PREFIX_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --location "$LOCATION" \ + --length 31 \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$PIP_PREFIX_NAME] public IP prefix for the NAT Gateway successfully created in the [$RESOURCE_GROUP_NAME] resource group" + else + echo "Failed to create [$PIP_PREFIX_NAME] public IP prefix for the NAT Gateway in the [$RESOURCE_GROUP_NAME] resource group" + exit 1 + fi +else + echo "[$PIP_PREFIX_NAME] public IP prefix for the NAT Gateway already exists in the [$RESOURCE_GROUP_NAME] resource group" +fi + +# Check if the NAT Gateway already exists +echo "Checking if [$NAT_GATEWAY_NAME] NAT Gateway actually exists in the [$RESOURCE_GROUP_NAME] resource group..." +$AZ network nat gateway show \ + --name "$NAT_GATEWAY_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$NAT_GATEWAY_NAME] NAT Gateway actually exists in the [$RESOURCE_GROUP_NAME] resource group" + echo "Creating [$NAT_GATEWAY_NAME] NAT Gateway in the [$RESOURCE_GROUP_NAME] resource group..." + + # Create the NAT Gateway + $AZ network nat gateway create \ + --name "$NAT_GATEWAY_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --location "$LOCATION" \ + --public-ip-prefixes "$PIP_PREFIX_NAME" \ + --idle-timeout 4 \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$NAT_GATEWAY_NAME] NAT Gateway successfully created in the [$RESOURCE_GROUP_NAME] resource group" + else + echo "Failed to create [$NAT_GATEWAY_NAME] NAT Gateway in the [$RESOURCE_GROUP_NAME] resource group" + exit 1 + fi +else + echo "[$NAT_GATEWAY_NAME] NAT Gateway already exists in the [$RESOURCE_GROUP_NAME] resource group" +fi + +# Check if the virtual network already exists +echo "Checking if [$VIRTUAL_NETWORK_NAME] virtual network actually exists in the [$RESOURCE_GROUP_NAME] resource group..." +$AZ network vnet show \ + --name "$VIRTUAL_NETWORK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$VIRTUAL_NETWORK_NAME] virtual network actually exists in the [$RESOURCE_GROUP_NAME] resource group" + echo "Creating [$VIRTUAL_NETWORK_NAME] virtual network in the [$RESOURCE_GROUP_NAME] resource group..." + + # Create the virtual network + $AZ network vnet create \ + --name "$VIRTUAL_NETWORK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --location "$LOCATION" \ + --address-prefixes "$VIRTUAL_NETWORK_ADDRESS_PREFIX" \ + --subnet-name "$WEBAPP_SUBNET_NAME" \ + --subnet-prefix "$WEBAPP_SUBNET_PREFIX" \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$VIRTUAL_NETWORK_NAME] virtual network successfully created in the [$RESOURCE_GROUP_NAME] resource group" + else + echo "Failed to create [$VIRTUAL_NETWORK_NAME] virtual network in the [$RESOURCE_GROUP_NAME] resource group" + exit + fi + + # Update the web app subnet to associate it with the NAT Gateway and the NSG + echo "Associating [$WEBAPP_SUBNET_NAME] subnet with the [$NAT_GATEWAY_NAME] NAT Gateway and the [$WEBAPP_SUBNET_NSG_NAME] network security group..." + + # Update the web app subnet to associate it with the NAT Gateway and the NSG + $AZ network vnet subnet update \ + --name "$WEBAPP_SUBNET_NAME" \ + --vnet-name "$VIRTUAL_NETWORK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --nat-gateway "$NAT_GATEWAY_NAME" \ + --network-security-group "$WEBAPP_SUBNET_NSG_NAME" \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$WEBAPP_SUBNET_NAME] subnet successfully associated with the [$NAT_GATEWAY_NAME] NAT Gateway and the [$WEBAPP_SUBNET_NSG_NAME] network security group" + else + echo "Failed to associate [$WEBAPP_SUBNET_NAME] subnet with the [$NAT_GATEWAY_NAME] NAT Gateway and the [$WEBAPP_SUBNET_NSG_NAME] network security group" + exit 1 + fi +else + echo "[$VIRTUAL_NETWORK_NAME] virtual network already exists in the [$RESOURCE_GROUP_NAME] resource group" +fi + +# Check if the subnet already exists +echo "Checking if [$PE_SUBNET_NAME] subnet actually exists in the [$VIRTUAL_NETWORK_NAME] virtual network..." +$AZ network vnet subnet show \ + --name "$PE_SUBNET_NAME" \ + --vnet-name "$VIRTUAL_NETWORK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$PE_SUBNET_NAME] subnet actually exists in the [$VIRTUAL_NETWORK_NAME] virtual network" + echo "Creating [$PE_SUBNET_NAME] subnet in the [$VIRTUAL_NETWORK_NAME] virtual network..." + + # Create the subnet + $AZ network vnet subnet create \ + --name "$PE_SUBNET_NAME" \ + --vnet-name "$VIRTUAL_NETWORK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --address-prefix "$PE_SUBNET_PREFIX" \ + --network-security-group "$PE_SUBNET_NSG_NAME" \ + --private-endpoint-network-policies "Disabled" \ + --private-link-service-network-policies "Disabled" \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$PE_SUBNET_NAME] subnet successfully created in the [$VIRTUAL_NETWORK_NAME] virtual network" + else + echo "Failed to create [$PE_SUBNET_NAME] subnet in the [$VIRTUAL_NETWORK_NAME] virtual network" + exit + fi +else + echo "[$PE_SUBNET_NAME] subnet already exists in the [$VIRTUAL_NETWORK_NAME] virtual network" +fi + +# Retrieve the virtual network resource id +echo "Getting [$VIRTUAL_NETWORK_NAME] virtual network resource id in the [$RESOURCE_GROUP_NAME] resource group..." +VIRTUAL_NETWORK_ID=$($AZ network vnet show \ + --name "$VIRTUAL_NETWORK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --only-show-errors \ + --query id \ + --output tsv) + +if [[ -n $VIRTUAL_NETWORK_ID ]]; then + echo "[$VIRTUAL_NETWORK_NAME] virtual network resource id retrieved successfully: $VIRTUAL_NETWORK_ID" +else + echo "Failed to retrieve [$VIRTUAL_NETWORK_NAME] virtual network resource id in the [$RESOURCE_GROUP_NAME] resource group" + exit +fi + +# Check if the private DNS Zone already exists +echo "Checking if [$PRIVATE_DNS_ZONE_NAME] private DNS zone actually exists in the [$RESOURCE_GROUP_NAME] resource group..." +$AZ network private-dns zone show \ + --name "$PRIVATE_DNS_ZONE_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$PRIVATE_DNS_ZONE_NAME] private DNS zone actually exists in the [$RESOURCE_GROUP_NAME] resource group" + echo "Creating [$PRIVATE_DNS_ZONE_NAME] private DNS zone in the [$RESOURCE_GROUP_NAME] resource group..." + + # Create the private DNS Zone + $AZ network private-dns zone create \ + --name "$PRIVATE_DNS_ZONE_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$PRIVATE_DNS_ZONE_NAME] private DNS zone successfully created in the [$RESOURCE_GROUP_NAME] resource group" + else + echo "Failed to create [$PRIVATE_DNS_ZONE_NAME] private DNS zone in the [$RESOURCE_GROUP_NAME] resource group" + exit + fi +else + echo "[$PRIVATE_DNS_ZONE_NAME] private DNS zone already exists in the [$RESOURCE_GROUP_NAME] resource group" +fi + +# Check if the virtual network link between the private DNS zone and the virtual network already exists +echo "Checking if [$VIRTUAL_NETWORK_LINK_NAME] virtual network link between [$PRIVATE_DNS_ZONE_NAME] private DNS zone and [$VIRTUAL_NETWORK_NAME] virtual network actually exists..." +$AZ network private-dns link vnet show \ + --name "$VIRTUAL_NETWORK_LINK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --zone-name "$PRIVATE_DNS_ZONE_NAME" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$VIRTUAL_NETWORK_LINK_NAME] virtual network link between [$PRIVATE_DNS_ZONE_NAME] private DNS zone and [$VIRTUAL_NETWORK_NAME] virtual network actually exists" + + echo "Creating [$VIRTUAL_NETWORK_LINK_NAME] virtual network link between [$PRIVATE_DNS_ZONE_NAME] private DNS zone and [$VIRTUAL_NETWORK_NAME] virtual network..." + + # Create the virtual network link between [$PRIVATE_DNS_ZONE_NAME] private DNS zone and [$VIRTUAL_NETWORK_NAME] virtual network + $AZ network private-dns link vnet create \ + --name "$VIRTUAL_NETWORK_LINK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --zone-name "$PRIVATE_DNS_ZONE_NAME" \ + --virtual-network "$VIRTUAL_NETWORK_ID" \ + --registration-enabled false \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$VIRTUAL_NETWORK_LINK_NAME] virtual network link between [$PRIVATE_DNS_ZONE_NAME] private DNS zone and [$VIRTUAL_NETWORK_NAME] virtual network successfully created" + else + echo "Failed to create [$VIRTUAL_NETWORK_LINK_NAME] virtual network link between [$PRIVATE_DNS_ZONE_NAME] private DNS zone and [$VIRTUAL_NETWORK_NAME] virtual network" + exit + fi +else + echo "[$VIRTUAL_NETWORK_LINK_NAME] virtual network link between [$PRIVATE_DNS_ZONE_NAME] private DNS zone and [$VIRTUAL_NETWORK_NAME] virtual network already exists" +fi + +# Check if the private endpoint already exists +echo "Checking if private endpoint [$PRIVATE_ENDPOINT_NAME] exists in the [$RESOURCE_GROUP_NAME] resource group..." +privateEndpointId=$($AZ network private-endpoint list \ + --resource-group $RESOURCE_GROUP_NAME \ + --only-show-errors \ + --query "[?name=='$PRIVATE_ENDPOINT_NAME'].id" \ + --output tsv) + +if [[ -z $privateEndpointId ]]; then + echo "Private endpoint [$PRIVATE_ENDPOINT_NAME] does not exist in the [$RESOURCE_GROUP_NAME] resource group" + echo "Creating [$PRIVATE_ENDPOINT_NAME] private endpoint for the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group..." + + # Create a private endpoint for the CosmosDB account + $AZ network private-endpoint create \ + --name "$PRIVATE_ENDPOINT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --location "$LOCATION" \ + --vnet-name "$VIRTUAL_NETWORK_NAME" \ + --subnet "$PE_SUBNET_NAME" \ + --private-connection-resource-id "$COSMOSDB_ACCOUNT_ID" \ + --group-id "$PRIVATE_ENDPOINT_GROUP" \ + --connection-name "mongodb-connection" \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "Private endpoint [$PRIVATE_ENDPOINT_NAME] successfully created for the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group" + else + echo "Failed to create a private endpoint for the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group" + exit + fi +else + echo "Private endpoint [$PRIVATE_ENDPOINT_NAME] already exists in the [$RESOURCE_GROUP_NAME] resource group" +fi + +# Check if the private DNS zone grou is already created for the CosmosDB account private endpoint +echo "Checking if the private DNS zone group [$PRIVATE_DNS_ZONE_GROUP_NAME] for the [$PRIVATE_ENDPOINT_NAME] private endpoint already exists..." +NAME=$($AZ network private-endpoint dns-zone-group show \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --endpoint-name "$PRIVATE_ENDPOINT_NAME" \ + --name "$PRIVATE_DNS_ZONE_GROUP_NAME" \ + --query name \ + --output tsv \ + --only-show-errors) + +if [[ -z $NAME ]]; then + echo "No private DNS zone group [$PRIVATE_DNS_ZONE_GROUP_NAME] for the [$PRIVATE_ENDPOINT_NAME] private endpoint actually exists" + echo "Creating private DNS zone group [$PRIVATE_DNS_ZONE_GROUP_NAME] for the [$PRIVATE_ENDPOINT_NAME] private endpoint..." + + # Create the private DNS zone group for the CosmosDB account private endpoint + $AZ network private-endpoint dns-zone-group create \ + --name "$PRIVATE_DNS_ZONE_GROUP_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --endpoint-name "$PRIVATE_ENDPOINT_NAME" \ + --private-dns-zone "$PRIVATE_DNS_ZONE_NAME" \ + --zone-name "mongodb-zone" \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "Private DNS zone group [$PRIVATE_DNS_ZONE_GROUP_NAME] for the [$PRIVATE_ENDPOINT_NAME] private endpoint successfully created" + else + echo "Failed to create private DNS zone group [$PRIVATE_DNS_ZONE_GROUP_NAME] for the [$PRIVATE_ENDPOINT_NAME] private endpoint" + exit + fi +else + echo "Private DNS zone group [$PRIVATE_DNS_ZONE_GROUP_NAME] for the [$PRIVATE_ENDPOINT_NAME] private endpoint already exists" +fi + +# Create app service plan +echo "Creating app service plan [$APP_SERVICE_PLAN_NAME]..." $AZ appservice plan create \ --resource-group "$RESOURCE_GROUP_NAME" \ --name "$APP_SERVICE_PLAN_NAME" \ @@ -143,32 +592,81 @@ $AZ appservice plan create \ --only-show-errors 1>/dev/null if [ $? -eq 0 ]; then - echo "App Service Plan [$APP_SERVICE_PLAN_NAME] created successfully." + echo "app service plan [$APP_SERVICE_PLAN_NAME] created successfully." +else + echo "Failed to create app service plan [$APP_SERVICE_PLAN_NAME]." + exit 1 +fi + +# Get the app service plan resource id +echo "Getting [$APP_SERVICE_PLAN_NAME] app service plan resource id in the [$RESOURCE_GROUP_NAME] resource group..." +APP_SERVICE_PLAN_ID=$($AZ appservice plan show \ + --name "$APP_SERVICE_PLAN_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query id \ + --output tsv \ + --only-show-errors) + +if [[ -n $APP_SERVICE_PLAN_ID ]]; then + echo "[$APP_SERVICE_PLAN_NAME] app service plan resource id retrieved successfully: $APP_SERVICE_PLAN_ID" else - echo "Failed to create App Service Plan [$APP_SERVICE_PLAN_NAME]." + echo "Failed to retrieve [$APP_SERVICE_PLAN_NAME] app service plan resource id in the [$RESOURCE_GROUP_NAME] resource group" exit 1 fi # Create the web app -echo "Creating web app [$WEB_APP_NAME]..." +echo "Creating web app [$WEBAPP_NAME]..." $AZ webapp create \ --resource-group "$RESOURCE_GROUP_NAME" \ --plan "$APP_SERVICE_PLAN_NAME" \ - --name "$WEB_APP_NAME" \ + --name "$WEBAPP_NAME" \ --runtime "$RUNTIME:$RUNTIME_VERSION" \ + --vnet "$VIRTUAL_NETWORK_NAME" \ + --subnet "$WEBAPP_SUBNET_NAME" \ --only-show-errors 1>/dev/null if [ $? -eq 0 ]; then - echo "Web app [$WEB_APP_NAME] created successfully." + echo "Web app [$WEBAPP_NAME] created successfully." +else + echo "Failed to create web app [$WEBAPP_NAME]." + exit 1 +fi + +# Enabling +echo "Enabling forced tunneling for web app [$WEBAPP_NAME] to route all outbound traffic through the virtual network..." + +$AZ resource update \ + --ids "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP_NAME/providers/Microsoft.Web/sites/$WEBAPP_NAME" \ + --set properties.outboundVnetRouting.allTraffic=true \ + --only-show-errors 1>/dev/null + +if [ $? -eq 0 ]; then + echo "Forced tunneling enabled for web app [$WEBAPP_NAME]." +else + echo "Failed to enable forced tunneling for web app [$WEBAPP_NAME]." + exit 1 +fi + +# Get the web app resource id +echo "Getting [$WEBAPP_NAME] web app resource id in the [$RESOURCE_GROUP_NAME] resource group..." +WEBAPP_ID=$($AZ webapp show \ + --name "$WEBAPP_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query id \ + --output tsv \ + --only-show-errors) + +if [[ -n $WEBAPP_ID ]]; then + echo "[$WEBAPP_NAME] web app resource id retrieved successfully: $WEBAPP_ID" else - echo "Failed to create web app [$WEB_APP_NAME]." + echo "Failed to retrieve [$WEBAPP_NAME] web app resource id in the [$RESOURCE_GROUP_NAME] resource group" exit 1 fi # Set web app settings -echo "Setting web app settings for [$WEB_APP_NAME]..." +echo "Setting web app settings for [$WEBAPP_NAME]..." $AZ webapp config appsettings set \ - --name $WEB_APP_NAME \ + --name $WEBAPP_NAME \ --resource-group $RESOURCE_GROUP_NAME \ --settings \ SCM_DO_BUILD_DURING_DEPLOYMENT='true' \ @@ -177,15 +675,252 @@ $AZ webapp config appsettings set \ COSMOSDB_DATABASE_NAME="$MONGODB_DATABASE_NAME" \ COSMOSDB_COLLECTION_NAME="$COLLECTION_NAME" \ LOGIN_NAME="$LOGIN_NAME" \ + WEBSITE_PORT="8000" \ --only-show-errors 1>/dev/null if [ $? -eq 0 ]; then - echo "Web app settings for [$WEB_APP_NAME] set successfully." + echo "Web app settings for [$WEBAPP_NAME] set successfully." else - echo "Failed to set web app settings for [$WEB_APP_NAME]." + echo "Failed to set web app settings for [$WEBAPP_NAME]." exit 1 fi +# Check if the log analytics workspace already exists +echo "Checking if [$LOG_ANALYTICS_NAME] Log Analytics workspace already exists in the [$RESOURCE_GROUP_NAME] resource group..." +$AZ monitor log-analytics workspace show \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --workspace-name "$LOG_ANALYTICS_NAME" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$LOG_ANALYTICS_NAME] Log Analytics workspace actually exists in the [$RESOURCE_GROUP_NAME] resource group" + echo "Creating [$LOG_ANALYTICS_NAME] Log Analytics workspace in the [$RESOURCE_GROUP_NAME] resource group..." + + # Create the Log Analytics workspace + $AZ monitor log-analytics workspace create \ + --name "$LOG_ANALYTICS_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --location "$LOCATION" \ + --query-access "Enabled" \ + --retention-time 30 \ + --sku "PerNode" \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$LOG_ANALYTICS_NAME] Log Analytics workspace successfully created in the [$RESOURCE_GROUP_NAME] resource group" + else + echo "Failed to create [$LOG_ANALYTICS_NAME] Log Analytics workspace in the [$RESOURCE_GROUP_NAME] resource group" + exit 1 + fi +else + echo "[$LOG_ANALYTICS_NAME] Log Analytics workspace already exists in the [$RESOURCE_GROUP_NAME] resource group" +fi + +# Check whether the diagnostic settings for the web app already exist +echo "Checking if [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_NAME] web app already exist..." +$AZ monitor diagnostic-settings show \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$WEBAPP_ID" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_NAME] web app actually exist" + echo "Creating [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_NAME] web app..." + + # Create the diagnostic settings for the web app to send logs to the Log Analytics workspace + $AZ monitor diagnostic-settings create \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$WEBAPP_ID" \ + --workspace "$LOG_ANALYTICS_NAME" \ + --logs '[ + {"category": "AppServiceHTTPLogs", "enabled": true}, + {"category": "AppServiceConsoleLogs", "enabled": true}, + {"category": "AppServiceAppLogs", "enabled": true}, + {"category": "AppServiceAuditLogs", "enabled": true}, + {"category": "AppServiceIPSecAuditLogs", "enabled": true}, + {"category": "AppServicePlatformLogs", "enabled": true}, + {"category": "AppServiceAuthenticationLogs", "enabled": true} + ]' \ + --metrics '[ + {"category": "AllMetrics", "enabled": true} + ]' \ + --only-show-errors 1>/dev/null + + + if [[ $? == 0 ]]; then + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_NAME] web app successfully created" + else + echo "Failed to create [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_NAME] web app" + exit 1 + fi +else + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_NAME] web app already exist" +fi + +# Check whether the diagnostic settings for the app service plan already exist +echo "Checking if [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$APP_SERVICE_PLAN_NAME] app service plan already exist..." +$AZ monitor diagnostic-settings show \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$APP_SERVICE_PLAN_ID" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$APP_SERVICE_PLAN_NAME] app service plan actually exist" + echo "Creating [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$APP_SERVICE_PLAN_NAME] app service plan..." + + # Create the diagnostic settings for the app service plan to send logs to the Log Analytics workspace + $AZ monitor diagnostic-settings create \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$APP_SERVICE_PLAN_ID" \ + --workspace "$LOG_ANALYTICS_NAME" \ + --metrics '[ + {"category": "AllMetrics", "enabled": true} + ]' \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$APP_SERVICE_PLAN_NAME] app service plan successfully created" + else + echo "Failed to create [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$APP_SERVICE_PLAN_NAME] app service plan" + exit 1 + fi +else + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$APP_SERVICE_PLAN_NAME] app service plan already exist" +fi + +# Check whether the diagnostic settings for the CosmosDB account already exist +echo "Checking if [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exist..." +$AZ monitor diagnostic-settings show \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$COSMOSDB_ACCOUNT_ID" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account actually exist" + echo "Creating [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account..." + + # Create the diagnostic settings for the CosmosDB account to send logs to the Log Analytics workspace + $AZ monitor diagnostic-settings create \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$COSMOSDB_ACCOUNT_ID" \ + --workspace "$LOG_ANALYTICS_NAME" \ + --logs '[ + {"category": "DataPlaneRequests", "enabled": true}, + {"category": "MongoRequests", "enabled": true} + ]' \ + --metrics '[ + {"category": "Requests", "enabled": true} + ]' \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account successfully created" + else + echo "Failed to create [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account" + exit 1 + fi +else + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exist" +fi + +# Check whether the diagnostic settings for the virtual network already exist +echo "Checking if [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$VIRTUAL_NETWORK_NAME] virtual network already exist..." +$AZ monitor diagnostic-settings show \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$VIRTUAL_NETWORK_ID" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$VIRTUAL_NETWORK_NAME] virtual network actually exist" + echo "Creating [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$VIRTUAL_NETWORK_NAME] virtual network..." + + # Create the diagnostic settings for the virtual network to send logs to the Log Analytics workspace + $AZ monitor diagnostic-settings create \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$VIRTUAL_NETWORK_ID" \ + --workspace "$LOG_ANALYTICS_NAME" \ + --logs '[ + {"category": "VMProtectionAlerts", "enabled": true} + ]' \ + --metrics '[ + {"category": "AllMetrics", "enabled": true} + ]' \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$VIRTUAL_NETWORK_NAME] virtual network successfully created" + else + echo "Failed to create [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$VIRTUAL_NETWORK_NAME] virtual network" + exit 1 + fi +else + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$VIRTUAL_NETWORK_NAME] virtual network already exist" +fi + +# Check whether the diagnostic settings for the network security group for the web app subnet already exist +echo "Checking if [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet already exist..." +$AZ monitor diagnostic-settings show \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$WEBAPP_SUBNET_NSG_ID" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet actually exist" + echo "Creating [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet..." + + # Create the diagnostic settings for the network security group for the web app subnet to send logs to the Log Analytics workspace + $AZ monitor diagnostic-settings create \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$WEBAPP_SUBNET_NSG_ID" \ + --workspace "$LOG_ANALYTICS_NAME" \ + --logs '[ + {"category": "NetworkSecurityGroupEvent", "enabled": true}, + {"category": "NetworkSecurityGroupRuleCounter", "enabled": true} + ]' \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet successfully created" + else + echo "Failed to create [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet" + exit 1 + fi +else + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$WEBAPP_SUBNET_NSG_NAME] network security group for the web app subnet already exist" +fi + +# Check whether the diagnostic settings for the network security group for the private endpoint subnet already exist +echo "Checking if [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet already exist..." +$AZ monitor diagnostic-settings show \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$PE_SUBNET_NSG_ID" \ + --only-show-errors &>/dev/null + +if [[ $? != 0 ]]; then + echo "No [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet actually exist" + echo "Creating [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet..." + + # Create the diagnostic settings for the network security group for the private endpoint subnet to send logs to the Log Analytics workspace + $AZ monitor diagnostic-settings create \ + --name "$DIAGNOSTIC_SETTINGS_NAME" \ + --resource "$PE_SUBNET_NSG_ID" \ + --workspace "$LOG_ANALYTICS_NAME" \ + --logs '[ + {"category": "NetworkSecurityGroupEvent", "enabled": true}, + {"category": "NetworkSecurityGroupRuleCounter", "enabled": true} + ]' \ + --only-show-errors 1>/dev/null + + if [[ $? == 0 ]]; then + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet successfully created" + else + echo "Failed to create [$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet" + exit 1 + fi +else + echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet already exist" +fi + # Change current directory to source folder cd "../src" || exit @@ -203,11 +938,11 @@ echo "Contents of the zip package [$ZIPFILE]:" unzip -l "$ZIPFILE" # Deploy the web app -echo "Deploying web app [$WEB_APP_NAME] with zip file [$ZIPFILE]..." +echo "Deploying web app [$WEBAPP_NAME] with zip file [$ZIPFILE]..." echo "Using standard $AZ webapp deploy command for AzureCloud environment." $AZ webapp deploy \ --resource-group "$RESOURCE_GROUP_NAME" \ - --name "$WEB_APP_NAME" \ + --name "$WEBAPP_NAME" \ --src-path "$ZIPFILE" \ --type zip \ --async true 1>/dev/null @@ -215,4 +950,8 @@ $AZ webapp deploy \ # Remove the zip package of the web app if [ -f "$ZIPFILE" ]; then rm "$ZIPFILE" -fi \ No newline at end of file +fi + +# Print the list of resources in the resource group +echo "Listing resources in resource group [$RESOURCE_GROUP_NAME]..." +az resource list --resource-group "$RESOURCE_GROUP_NAME" --output table diff --git a/samples/web-app-cosmosdb-mongodb-api/python/scripts/validate.sh b/samples/web-app-cosmosdb-mongodb-api/python/scripts/validate.sh index 9d836f3..1216151 100755 --- a/samples/web-app-cosmosdb-mongodb-api/python/scripts/validate.sh +++ b/samples/web-app-cosmosdb-mongodb-api/python/scripts/validate.sh @@ -1,6 +1,21 @@ #!/bin/bash # Variables +PREFIX='local' +SUFFIX='test' +RESOURCE_GROUP_NAME="${PREFIX}-rg" +LOG_ANALYTICS_NAME="${PREFIX}-log-analytics-${SUFFIX}" +WEBAPP_SUBNET_NSG_NAME="${PREFIX}-webapp-subnet-nsg-${SUFFIX}" +PE_SUBNET_NSG_NAME="${PREFIX}-pe-subnet-nsg-${SUFFIX}" +NAT_GATEWAY_NAME="${PREFIX}-nat-gateway-${SUFFIX}" +VIRTUAL_NETWORK_NAME="${PREFIX}-vnet-${SUFFIX}" +PRIVATE_DNS_ZONE_NAME="privatelink.mongo.cosmos.azure.com" +PRIVATE_ENDPOINT_NAME="${PREFIX}-mongodb-pe-${SUFFIX}" +APP_SERVICE_PLAN_NAME="${PREFIX}-app-service-plan-${SUFFIX}" +WEBAPP_NAME="${PREFIX}-webapp-${SUFFIX}" +COSMOSDB_ACCOUNT_NAME="${PREFIX}-mongodb-${SUFFIX}" +MONGODB_DATABASE_NAME="sampledb" +COLLECTION_NAME="activities" ENVIRONMENT=$(az account show --query environmentName --output tsv) # Choose the appropriate CLI based on the environment @@ -13,38 +28,118 @@ else fi # Check resource group +echo -e "[$RESOURCE_GROUP_NAME] resource group:\n" $AZ group show \ ---name local-rg \ ---output table + --name "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors -# List resources -$AZ resource list \ ---resource-group local-rg \ ---output table +# Check App Service Plan +echo -e "\n[$APP_SERVICE_PLAN_NAME] app service plan:\n" +$AZ appservice plan show \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --name "$APP_SERVICE_PLAN_NAME" \ + --output table \ + --only-show-errors # Check Azure Web App +echo -e "\n[$WEBAPP_NAME] web app:\n" $AZ webapp show \ ---name local-webapp-test \ ---resource-group local-rg \ ---output table + --name "$WEBAPP_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors # Check Azure CosmosDB account +echo -e "\n[$COSMOSDB_ACCOUNT_NAME] cosmosdb account:\n" $AZ cosmosdb show \ ---name local-mongodb-test \ ---resource-group local-rg \ ---output table + --name "$COSMOSDB_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query '{Name:name,Location:location,ResourceGroup:resourceGroup,DocumentEndpoint:documentEndpoint}' \ + --output table \ + --only-show-errors # Check MongoDB database +echo -e "\n[$MONGODB_DATABASE_NAME] mongodb database:\n" $AZ cosmosdb mongodb database show \ ---name sampledb \ ---account-name local-mongodb-test \ ---resource-group local-rg \ ---output table + --name "$MONGODB_DATABASE_NAME" \ + --account-name "$COSMOSDB_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query '{Name:name,ResourceGroup:resourceGroup}' \ + --output table \ + --only-show-errors # Check MongoDB collection +echo -e "\n[$COLLECTION_NAME] mongodb collection:\n" $AZ cosmosdb mongodb collection show \ ---name activities \ ---database-name sampledb \ ---account-name local-mongodb-test \ ---resource-group local-rg \ ---output table \ No newline at end of file + --name "$COLLECTION_NAME" \ + --database-name "$MONGODB_DATABASE_NAME" \ + --account-name "$COSMOSDB_ACCOUNT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Log Analytics Workspace +echo -e "\n[$LOG_ANALYTICS_NAME] log analytics workspace:\n" +$AZ monitor log-analytics workspace show \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --workspace-name "$LOG_ANALYTICS_NAME" \ + --query '{Name:name,Location:location,ResourceGroup:resourceGroup}' \ + --output table \ + --only-show-errors + +# Check NAT Gateway +echo -e "\n[$NAT_GATEWAY_NAME] nat gateway:\n" +$AZ network nat gateway show \ + --name "$NAT_GATEWAY_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Virtual Network +echo -e "\n[$VIRTUAL_NETWORK_NAME] virtual network:\n" +$AZ network vnet show \ + --name "$VIRTUAL_NETWORK_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Private DNS Zone +echo -e "\n[$PRIVATE_DNS_ZONE_NAME] private dns zone:\n" +$AZ network private-dns zone show \ + --name "$PRIVATE_DNS_ZONE_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --query '{Name:name,ResourceGroup:resourceGroup,RecordSets:recordSets,VirtualNetworkLinks:virtualNetworkLinks}' \ + --output table \ + --only-show-errors + +# Check Private Endpoint +echo -e "\n[$PRIVATE_ENDPOINT_NAME] private endpoint:\n" +$AZ network private-endpoint show \ + --name "$PRIVATE_ENDPOINT_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Web App Subnet NSG +echo -e "\n[$WEBAPP_SUBNET_NSG_NAME] network security group:\n" +$AZ network nsg show \ + --name "$WEBAPP_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# Check Private Endpoint Subnet NSG +echo -e "\n[$PE_SUBNET_NSG_NAME] network security group:\n" +$AZ network nsg show \ + --name "$PE_SUBNET_NSG_NAME" \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors + +# List resources +echo -e "\n[$RESOURCE_GROUP_NAME] all resources:\n" +$AZ resource list \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --output table \ + --only-show-errors \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/src/.env.azure b/samples/web-app-cosmosdb-mongodb-api/python/src/.env.azure new file mode 100644 index 0000000..6f4d1dd --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/src/.env.azure @@ -0,0 +1,14 @@ +AZURE_CLIENT_ID=211c8652-a609-453a-bdb3-eda8405f5c4c +AZURE_CLIENT_SECRET=D768Q~WrZz6STebgsEu28-.sLQx2kEhTpmpnLcPw +AZURE_TENANT_ID=24083153-e8cb-43bf-a098-be24dc3668f7 +AZURE_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000 +COSMOSDB_CONNECTION_STRING=mongodb://local-mongodb-test:q24CL9kCyafqa3cyMl7bujpx4SiuYSRDZZnQ7Z33ZrdUdjKw7rxyfXGjBrS4vWZx2i6tgKpKQN7YACDbFdtHPQ==@local-mongodb-test.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@local-mongodb-test@ +#COSMOSDB_BASE_URL=https://local-mongodb-test.documents.azure.com:443/ +#COSMOSDB_BASE_URL=https://localhost:8081/ +COSMOSDB_DATABASE_NAME=sampledb +COSMOSDB_COLLECTION_NAME=activities +LOGIN_NAME=paolo +REQUESTS_CA_BUNDLE=/home/paolo/.localstack/azure/ca.crt +#HTTP_PROXY=http://127.0.0.1:4510 +#HTTPS_PROXY=http://127.0.0.1:4510 +DEBUG=True \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/src/.zip b/samples/web-app-cosmosdb-mongodb-api/python/src/.zip new file mode 100644 index 0000000..ba51ef8 Binary files /dev/null and b/samples/web-app-cosmosdb-mongodb-api/python/src/.zip differ diff --git a/samples/web-app-cosmosdb-mongodb-api/python/src/requirements.txt b/samples/web-app-cosmosdb-mongodb-api/python/src/requirements.txt index bf21e81..95183b0 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/src/requirements.txt +++ b/samples/web-app-cosmosdb-mongodb-api/python/src/requirements.txt @@ -1,8 +1,5 @@ Flask==3.1.0 azure-identity==1.25.1 -azure-mgmt-cosmosdb==9.8.0 -azure-mgmt-cosmosdbforpostgresql==1.0.0 pymongo==4.15.3 gunicorn==23.0.0 -python-dotenv==1.1.1 - +python-dotenv==1.1.1 \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/src/static/favicon.ico:Zone.Identifier b/samples/web-app-cosmosdb-mongodb-api/python/src/static/favicon.ico:Zone.Identifier new file mode 100644 index 0000000..603e0df --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/src/static/favicon.ico:Zone.Identifier @@ -0,0 +1,4 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=https://portal.azure.com/Content/favicon.ico +HostUrl=https://portal.azure.com/Content/favicon.ico diff --git a/samples/web-app-cosmosdb-mongodb-api/python/src/templates/index.html b/samples/web-app-cosmosdb-mongodb-api/python/src/templates/index.html index 7259abe..d9e0cfa 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/src/templates/index.html +++ b/samples/web-app-cosmosdb-mongodb-api/python/src/templates/index.html @@ -8,13 +8,14 @@
-