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: ![Architecture Diagram](./images/architecture.png) -- **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 @@ -
+
Summer Banner

Vacation Planner

-
+
- - + + +
@@ -22,14 +23,17 @@

Vacation Planner

Activity - Action + Action {% for activity in activities %} {{ activity[1] }} - + + Edit + +
@@ -38,12 +42,28 @@

Vacation Planner

{% else %} - No vacation plans yet! + No vacation plans yet! {% endfor %}
+ diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/README.md b/samples/web-app-cosmosdb-mongodb-api/python/terraform/README.md index 81f80a2..4c62037 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/terraform/README.md +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/README.md @@ -27,17 +27,25 @@ For more information, see [Get started with the az tool on LocalStack](https://a ## Architecture Overview -The [main.tf](main.tf) Terraform module creates the following Azure resources: - -1. [Azure Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-cli): Logical container for all resources in the sample. -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 the CosmosDB-backed MongoDB collection. All resources are provisioned and configured using Terraform for easy reproducibility and local development with LocalStack for Azure. +The Terraform 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). ## Provisioning Scripts @@ -109,12 +117,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 @@ -127,41 +150,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/terraform/deploy.sh b/samples/web-app-cosmosdb-mongodb-api/python/terraform/deploy.sh index 0c7b013..3dc11b3 100755 --- a/samples/web-app-cosmosdb-mongodb-api/python/terraform/deploy.sh +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/deploy.sh @@ -1,7 +1,7 @@ #!/bin/bash # Variables -PREFIX='webdb' +PREFIX='ciao' SUFFIX='test' LOCATION='westeurope' CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -20,32 +20,46 @@ else AZ="az" fi +# Intialize Terraform echo "Initializing Terraform..." terraform init -upgrade # Run terraform plan and check for errors echo "Planning Terraform deployment..." terraform plan -out=tfplan \ - -var="prefix=$PREFIX" \ - -var="suffix=$SUFFIX" \ - -var="location=$LOCATION" + -var "prefix=$PREFIX" \ + -var "suffix=$SUFFIX" \ + -var "location=$LOCATION" + +if [[ $? != 0 ]]; then + echo "Terraform plan failed. Exiting." + exit 1 +fi # Apply the Terraform configuration echo "Applying Terraform configuration..." terraform apply -auto-approve tfplan if [[ $? != 0 ]]; then - echo "Terraform apply failed. Exiting." - exit 1 + echo "Terraform apply failed. Exiting." + exit 1 fi # Get the output values RESOURCE_GROUP_NAME=$(terraform output -raw resource_group_name) WEB_APP_NAME=$(terraform output -raw web_app_name) +ACCOUNT_NAME=$(terraform output -raw cosmosdb_account_name) + +if [[ -z "$RESOURCE_GROUP_NAME" || -z "$WEB_APP_NAME" || -z "$ACCOUNT_NAME" ]]; then + echo "Resource Group Name, Web App Name, or Cosmos DB Account Name is empty. Exiting." + exit 1 +fi -# Print the variables -echo "Resource Group: $RESOURCE_GROUP_NAME" -echo "Web App: $WEB_APP_NAME" +# Print the application settings of the web app +echo "Retrieving application settings for web app [$WEB_APP_NAME]..." +$AZ webapp config appsettings list \ + --resource-group "$RESOURCE_GROUP_NAME" \ + --name "$WEB_APP_NAME" # Change current directory to source folder cd "../src" || exit @@ -59,6 +73,7 @@ fi echo "Creating zip package of the web app..." zip -r "$ZIPFILE" app.py mongodb.py static templates requirements.txt +# Deploy the web app # Deploy the web app echo "Deploying web app [$WEB_APP_NAME] with zip file [$ZIPFILE]..." $AZ webapp deploy \ @@ -72,3 +87,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/terraform/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/main.tf index 84ca719..c7ba719 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/terraform/main.tf +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/main.tf @@ -1,9 +1,21 @@ # Local Variables locals { - resource_group_name = "${var.prefix}-rg" - cosmosdb_account_name = "${var.prefix}-mongodb-${var.suffix}" - app_service_plan_name = "${var.prefix}-app-service-plan-${var.suffix}" - web_app_name = "${var.prefix}-webapp-${var.suffix}" + prefix = lower(var.prefix) + suffix = lower(var.suffix) + resource_group_name = "${var.prefix}-rg" + log_analytics_name = "${local.prefix}-log-analytics-${local.suffix}" + storage_account_name = "${local.prefix}datastore${local.suffix}" + virtual_network_name = "${local.prefix}-vnet-${local.suffix}" + nat_gateway_name = "${local.prefix}-nat-gateway-${local.suffix}" + private_endpoint_name = "${local.prefix}-mongodb-pe-${local.suffix}" + network_security_group_name = "${local.prefix}-default-nsg-${local.suffix}" + cosmosdb_account_name = "${local.prefix}-mongodb-${local.suffix}" + app_service_plan_name = "${local.prefix}-app-service-plan-${local.suffix}" + web_app_name = "${local.prefix}-webapp-${local.suffix}" +} + +# Data Sources +data "azurerm_client_config" "current" { } # Create a resource group @@ -13,130 +25,157 @@ resource "azurerm_resource_group" "example" { tags = var.tags } -# Create a cosmosdb account -resource "azurerm_cosmosdb_account" "example" { - name = local.cosmosdb_account_name +# Create a log analytics workspace +module "log_analytics_workspace" { + source = "./modules/log_analytics" + name = local.log_analytics_name + location = var.location + resource_group_name = azurerm_resource_group.example.name + tags = var.tags +} + +# Create a virtual network with subnets +module "virtual_network" { + source = "./modules/virtual_network" resource_group_name = azurerm_resource_group.example.name - location = azurerm_resource_group.example.location - offer_type = "Standard" - kind = "MongoDB" - mongo_server_version = var.mongodb_server_version - automatic_failover_enabled = false + location = var.location + vnet_name = local.virtual_network_name + address_space = var.vnet_address_space + log_analytics_workspace_id = module.log_analytics_workspace.id tags = var.tags - consistency_policy { - consistency_level = var.consistency_level - max_interval_in_seconds = 300 - max_staleness_prefix = 100000 - } + subnets = [ + { + name : var.webapp_subnet_name + address_prefixes : var.webapp_subnet_address_prefix + private_endpoint_network_policies : "Enabled" + private_link_service_network_policies_enabled : false + delegation : "Microsoft.Web/serverFarms" + }, + { + name : var.pe_subnet_name + address_prefixes : var.pe_subnet_address_prefix + private_endpoint_network_policies : "Enabled" + private_link_service_network_policies_enabled : false + delegation : null + } + ] +} - geo_location { - location = var.primary_region - failover_priority = 0 +# Create a network security group and associate it with the default subnet +module "network_security_group" { + source = "./modules/network_security_group" + name = local.network_security_group_name + resource_group_name = azurerm_resource_group.example.name + location = var.location + log_analytics_workspace_id = module.log_analytics_workspace.id + tags = var.tags + subnet_ids = { + (var.webapp_subnet_name) = module.virtual_network.subnet_ids[var.webapp_subnet_name] } - geo_location { - location = var.secondary_region - failover_priority = 1 - } +} - lifecycle { - ignore_changes = [ - tags - ] +# Create a NAT gateway and associate it with the default subnet +module "nat_gateway" { + source = "./modules/nat_gateway" + name = local.nat_gateway_name + resource_group_name = azurerm_resource_group.example.name + location = var.location + sku_name = var.nat_gateway_sku_name + idle_timeout_in_minutes = var.nat_gateway_idle_timeout_in_minutes + zones = var.nat_gateway_zones + subnet_ids = { + (var.webapp_subnet_name) = module.virtual_network.subnet_ids[var.webapp_subnet_name] } + tags = var.tags } -resource "azurerm_cosmosdb_mongo_database" "example" { - name = var.cosmosdb_database_name +# Create a private DNS zone for the CosmosDB MongoDB account and link it to the virtual network +module "private_dns_zone" { + source = "./modules/private_dns_zone" + name = "privatelink.mongo.cosmos.azure.com" resource_group_name = azurerm_resource_group.example.name - account_name = azurerm_cosmosdb_account.example.name - throughput = 400 + tags = var.tags + virtual_networks_to_link = { + (module.virtual_network.name) = { + subscription_id = data.azurerm_client_config.current.subscription_id + resource_group_name = azurerm_resource_group.example.name + } + } } -resource "azurerm_cosmosdb_mongo_collection" "example" { - name = var.cosmosdb_collection_name - resource_group_name = azurerm_resource_group.example.name - account_name = azurerm_cosmosdb_account.example.name - database_name = azurerm_cosmosdb_mongo_database.example.name - - default_ttl_seconds = "777" - shard_key = "username" - throughput = 400 +# Create a private endpoint for the CosmosDB MongoDB account in the pe_subnet subnet +module "private_endpoint" { + source = "./modules/private_endpoint" + name = local.private_endpoint_name + location = var.location + resource_group_name = azurerm_resource_group.example.name + subnet_id = module.virtual_network.subnet_ids[var.pe_subnet_name] + tags = var.tags + private_connection_resource_id = module.cosmosdb_mongodb.id + is_manual_connection = false + subresource_name = "mongodb" + private_dns_zone_group_name = "private-dns-zone-group" + private_dns_zone_group_ids = [module.private_dns_zone.id] +} - # Dynamically create the 'index' blocks using a for_each loop over the variable - dynamic "index" { - # The for_each expression iterates over the list of keys from the variable - for_each = var.mongodb_index_keys - content { - # The value of the current item in the iteration (e.g., "$**", "_id", etc.) - keys = [index.value] - } - } +# Create CosmosDB MongoDB resources using module +module "cosmosdb_mongodb" { + source = "./modules/cosmosdb_mongodb" + name = local.cosmosdb_account_name + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + mongo_server_version = var.mongodb_server_version + consistency_level = var.consistency_level + primary_region = var.primary_region + secondary_region = var.secondary_region + database_name = var.cosmosdb_database_name + database_throughput = var.database_throughput + collection_name = var.cosmosdb_collection_name + index_keys = var.mongodb_index_keys + log_analytics_workspace_id = module.log_analytics_workspace.id + tags = var.tags } - -# Create a service plan -resource "azurerm_service_plan" "example" { - name = local.app_service_plan_name - resource_group_name = azurerm_resource_group.example.name - location = azurerm_resource_group.example.location - sku_name = var.sku_name - os_type = var.os_type - zone_balancing_enabled = var.zone_balancing_enabled - tags = var.tags - lifecycle { - ignore_changes = [ - tags - ] - } +# Create App Service Plan using module +module "app_service_plan" { + source = "./modules/app_service_plan" + name = local.app_service_plan_name + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku_name = var.sku_name + os_type = var.os_type + zone_balancing_enabled = var.zone_balancing_enabled + log_analytics_workspace_id = module.log_analytics_workspace.id + tags = var.tags } -# Create a web app -resource "azurerm_linux_web_app" "example" { +# Create Web App using module +module "web_app" { + source = "./modules/web_app" name = local.web_app_name resource_group_name = azurerm_resource_group.example.name location = azurerm_resource_group.example.location - service_plan_id = azurerm_service_plan.example.id + service_plan_id = module.app_service_plan.id https_only = var.https_only + virtual_network_subnet_id = module.virtual_network.subnet_ids[var.webapp_subnet_name] + vnet_route_all_enabled = true public_network_access_enabled = var.public_network_access_enabled - client_affinity_enabled = false + always_on = var.always_on + http2_enabled = var.http2_enabled + minimum_tls_version = var.minimum_tls_version + python_version = var.python_version + repo_url = var.repo_url + log_analytics_workspace_id = module.log_analytics_workspace.id tags = var.tags - identity { - type = "SystemAssigned" - } - - site_config { - always_on = var.always_on - http2_enabled = var.http2_enabled - minimum_tls_version = var.minimum_tls_version - application_stack { - python_version = var.python_version - } - } - app_settings = { SCM_DO_BUILD_DURING_DEPLOYMENT = "true" - COSMOSDB_CONNECTION_STRING = azurerm_cosmosdb_account.example.primary_mongodb_connection_string - COSMOSDB_DATABASE_NAME = azurerm_cosmosdb_mongo_database.example.name + COSMOSDB_CONNECTION_STRING = module.cosmosdb_mongodb.primary_mongodb_connection_string + COSMOSDB_DATABASE_NAME = module.cosmosdb_mongodb.database_name COSMOSDB_COLLECTION_NAME = var.cosmosdb_collection_name LOGIN_NAME = var.login_name + WEBSITE_PORT = var.website_port } - - lifecycle { - ignore_changes = [ - tags - ] - } -} - -# Deploy code from a public GitHub repo -resource "azurerm_app_service_source_control" "example" { - count = var.repo_url == "" ? 0 : 1 - app_id = azurerm_linux_web_app.example.id - repo_url = var.repo_url - branch = "main" - use_manual_integration = true - use_mercurial = false } \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/app_service_plan/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/app_service_plan/main.tf new file mode 100644 index 0000000..0f67edb --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/app_service_plan/main.tf @@ -0,0 +1,29 @@ +resource "azurerm_service_plan" "example" { + name = var.name + resource_group_name = var.resource_group_name + location = var.location + sku_name = var.sku_name + os_type = var.os_type + zone_balancing_enabled = var.zone_balancing_enabled + tags = var.tags + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +resource "azurerm_monitor_diagnostic_setting" "example" { + name = "DiagnosticsSettings" + target_resource_id = azurerm_service_plan.example.id + log_analytics_workspace_id = var.log_analytics_workspace_id + + enabled_log { + category = "VMProtectionAlerts" + } + + enabled_metric { + category = "AllMetrics" + } +} \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/app_service_plan/outputs.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/app_service_plan/outputs.tf new file mode 100644 index 0000000..f1455ea --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/app_service_plan/outputs.tf @@ -0,0 +1,19 @@ +output "id" { + value = azurerm_service_plan.example.id + description = "Specifies the resource id of the App Service Plan" +} + +output "name" { + value = azurerm_service_plan.example.name + description = "Specifies the name of the App Service Plan" +} + +output "location" { + value = azurerm_service_plan.example.location + description = "Specifies the location of the App Service Plan" +} + +output "resource_group_name" { + value = azurerm_service_plan.example.resource_group_name + description = "Specifies the resource group name of the App Service Plan" +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/app_service_plan/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/app_service_plan/variables.tf new file mode 100644 index 0000000..e543066 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/app_service_plan/variables.tf @@ -0,0 +1,42 @@ +variable "resource_group_name" { + description = "(Required) Specifies the name of the resource group." + type = string +} + +variable "location" { + description = "(Required) Specifies the location for the App Service Plan." + type = string +} + +variable "name" { + description = "(Required) Specifies the name of the App Service Plan." + type = string +} + +variable "sku_name" { + description = "(Required) Specifies the SKU name for the App Service Plan." + type = string +} + +variable "os_type" { + description = "(Required) Specifies the O/S type for the App Services to be hosted in this plan." + type = string + default = "Linux" +} + +variable "zone_balancing_enabled" { + description = "(Optional) Should the Service Plan balance across Availability Zones in the region." + type = bool + default = false +} + +variable "tags" { + description = "(Optional) Specifies the tags to be applied to the resources." + type = map(any) + default = {} +} + +variable "log_analytics_workspace_id" { + description = "Specifies the resource id of the Azure Log Analytics workspace." + type = string +} \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/cosmosdb_mongodb/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/cosmosdb_mongodb/main.tf new file mode 100644 index 0000000..1cd082c --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/cosmosdb_mongodb/main.tf @@ -0,0 +1,78 @@ +resource "azurerm_cosmosdb_account" "example" { + name = var.name + resource_group_name = var.resource_group_name + location = var.location + offer_type = "Standard" + kind = "MongoDB" + mongo_server_version = var.mongo_server_version + automatic_failover_enabled = false + tags = var.tags + + consistency_policy { + consistency_level = var.consistency_level + max_interval_in_seconds = 300 + max_staleness_prefix = 100000 + } + + geo_location { + location = var.primary_region + failover_priority = 0 + } + + geo_location { + location = var.secondary_region + failover_priority = 1 + } + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +resource "azurerm_cosmosdb_mongo_database" "example" { + name = var.database_name + resource_group_name = var.resource_group_name + account_name = azurerm_cosmosdb_account.example.name + throughput = var.database_throughput +} + +resource "azurerm_cosmosdb_mongo_collection" "example" { + name = var.collection_name + resource_group_name = var.resource_group_name + account_name = azurerm_cosmosdb_account.example.name + database_name = azurerm_cosmosdb_mongo_database.example.name + + default_ttl_seconds = var.default_ttl_seconds + shard_key = var.shard_key + throughput = var.collection_throughput + + # Dynamically create the 'index' blocks using a for_each loop over the variable + dynamic "index" { + # The for_each expression iterates over the list of keys from the variable + for_each = var.index_keys + content { + # The value of the current item in the iteration (e.g., "$**", "_id", etc.) + keys = [index.value] + } + } +} + +resource "azurerm_monitor_diagnostic_setting" "example" { + name = "DiagnosticsSettings" + target_resource_id = azurerm_cosmosdb_account.example.id + log_analytics_workspace_id = var.log_analytics_workspace_id + + enabled_log { + category = "DataPlaneRequests" + } + + enabled_log { + category = "MongoRequests" + } + + enabled_metric { + category = "Requests" + } +} \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/cosmosdb_mongodb/outputs.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/cosmosdb_mongodb/outputs.tf new file mode 100644 index 0000000..09bcd7e --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/cosmosdb_mongodb/outputs.tf @@ -0,0 +1,30 @@ +output "id" { + value = azurerm_cosmosdb_account.example.id + description = "Specifies the resource id of the Cosmos DB account" +} + +output "name" { + value = azurerm_cosmosdb_account.example.name + description = "Specifies the name of the Cosmos DB account" +} + +output "endpoint" { + value = azurerm_cosmosdb_account.example.endpoint + description = "Specifies the endpoint of the Cosmos DB account" +} + +output "primary_mongodb_connection_string" { + value = azurerm_cosmosdb_account.example.primary_mongodb_connection_string + description = "Specifies the primary MongoDB connection string" + sensitive = true +} + +output "database_name" { + value = azurerm_cosmosdb_mongo_database.example.name + description = "Specifies the name of the MongoDB database" +} + +output "collection_name" { + value = azurerm_cosmosdb_mongo_collection.example.name + description = "Specifies the name of the MongoDB collection" +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/cosmosdb_mongodb/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/cosmosdb_mongodb/variables.tf new file mode 100644 index 0000000..71e3cbe --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/cosmosdb_mongodb/variables.tf @@ -0,0 +1,87 @@ +variable "resource_group_name" { + description = "(Required) Specifies the name of the resource group." + type = string +} + +variable "location" { + description = "(Required) Specifies the location for the Cosmos DB account." + type = string +} + +variable "name" { + description = "(Required) Specifies the name of the Cosmos DB account." + type = string +} + +variable "mongo_server_version" { + description = "(Optional) Specifies the version of MongoDB API for the Azure Cosmos DB account." + type = string + default = "7.0" +} + +variable "consistency_level" { + description = "(Required) Specifies the consistency level for the Azure Cosmos DB account." + type = string + default = "Eventual" +} + +variable "primary_region" { + description = "(Required) Specifies the primary region for the Azure Cosmos DB account." + type = string +} + +variable "secondary_region" { + description = "(Required) Specifies the secondary region for the Azure Cosmos DB account." + type = string +} + +variable "database_name" { + description = "(Required) Specifies the name of the MongoDB database." + type = string +} + +variable "database_throughput" { + description = "(Optional) Specifies the throughput for the MongoDB database." + type = number + default = 400 +} + +variable "collection_name" { + description = "(Required) Specifies the name of the MongoDB collection." + type = string +} + +variable "collection_throughput" { + description = "(Optional) Specifies the throughput for the MongoDB collection." + type = number + default = 400 +} + +variable "default_ttl_seconds" { + description = "(Optional) Specifies the default TTL in seconds for documents in the collection." + type = string + default = "777" +} + +variable "shard_key" { + description = "(Optional) Specifies the shard key for the MongoDB collection." + type = string + default = "username" +} + +variable "index_keys" { + description = "A list of field names for which to create single-field indexes on the MongoDB collection." + type = list(string) + default = ["_id"] +} + +variable "tags" { + description = "(Optional) Specifies the tags to be applied to the resources." + type = map(any) + default = {} +} + +variable "log_analytics_workspace_id" { + description = "Specifies the resource id of the Azure Log Analytics workspace." + type = string +} \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/log_analytics/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/log_analytics/main.tf new file mode 100644 index 0000000..2f88414 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/log_analytics/main.tf @@ -0,0 +1,14 @@ +resource "azurerm_log_analytics_workspace" "example" { + name = var.name + location = var.location + resource_group_name = var.resource_group_name + sku = var.sku + tags = var.tags + retention_in_days = var.retention_in_days != "" ? var.retention_in_days : null + + lifecycle { + ignore_changes = [ + tags + ] + } +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/log_analytics/output.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/log_analytics/output.tf new file mode 100644 index 0000000..fe2c398 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/log_analytics/output.tf @@ -0,0 +1,30 @@ +output "id" { + value = azurerm_log_analytics_workspace.example.id + description = "Specifies the resource id of the log analytics workspace" +} + +output "location" { + value = azurerm_log_analytics_workspace.example.location + description = "Specifies the location of the log analytics workspace" +} + +output "name" { + value = azurerm_log_analytics_workspace.example.name + description = "Specifies the name of the log analytics workspace" +} + +output "resource_group_name" { + value = azurerm_log_analytics_workspace.example.resource_group_name + description = "Specifies the name of the resource group that contains the log analytics workspace" +} + +output "workspace_id" { + value = azurerm_log_analytics_workspace.example.workspace_id + description = "Specifies the workspace id of the log analytics workspace" +} + +output "primary_shared_key" { + value = azurerm_log_analytics_workspace.example.primary_shared_key + description = "Specifies the workspace key of the log analytics workspace" + sensitive = true +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/log_analytics/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/log_analytics/variables.tf new file mode 100644 index 0000000..2db6a01 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/log_analytics/variables.tf @@ -0,0 +1,37 @@ +variable "resource_group_name" { + description = "(Required) Specifies the name of the resource group." + type = string +} + +variable "location" { + description = "(Required) Specifies the location of the Azure Log Analytics workspace" + type = string +} + +variable "name" { + description = "(Required) Specifies the name of the Azure Log Analytics workspace" + type = string +} + +variable "sku" { + description = "(Optional) Specifies the sku of the Azure Log Analytics workspace" + type = string + default = "PerGB2018" + + validation { + condition = contains(["Free", "Standalone", "PerNode", "PerGB2018"], var.sku) + error_message = "The log analytics sku is incorrect." + } +} + +variable "tags" { + description = "(Optional) Specifies the tags of the Azure Log Analytics workspace." + type = map(any) + default = {} +} + +variable "retention_in_days" { + description = " (Optional) Specifies the workspace data retention in days. Possible values are either 7 (Free Tier only) or range between 30 and 730." + type = number + default = 30 +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/nat_gateway/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/nat_gateway/main.tf new file mode 100644 index 0000000..cc384af --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/nat_gateway/main.tf @@ -0,0 +1,42 @@ +resource "azurerm_public_ip" "example" { + name = "${var.name}PublicIp" + location = var.location + resource_group_name = var.resource_group_name + allocation_method = "Static" + sku = "Standard" + zones = var.zones + tags = var.tags + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +resource "azurerm_nat_gateway" "example" { + name = var.name + location = var.location + resource_group_name = var.resource_group_name + sku_name = var.sku_name + idle_timeout_in_minutes = var.idle_timeout_in_minutes + zones = var.zones + tags = var.tags + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +resource "azurerm_nat_gateway_public_ip_association" "example" { + nat_gateway_id = azurerm_nat_gateway.example.id + public_ip_address_id = azurerm_public_ip.example.id +} + +resource "azurerm_subnet_nat_gateway_association" "example" { + for_each = var.subnet_ids + subnet_id = each.value + nat_gateway_id = azurerm_nat_gateway.example.id +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/nat_gateway/output.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/nat_gateway/output.tf new file mode 100644 index 0000000..1e3fd03 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/nat_gateway/output.tf @@ -0,0 +1,14 @@ +output "name" { + value = azurerm_nat_gateway.example.name + description = "Specifies the name of the Azure NAT Gateway" +} + +output "id" { + value = azurerm_nat_gateway.example.id + description = "Specifies the resource id of the Azure NAT Gateway" +} + +output "public_ip_address" { + value = azurerm_public_ip.example.ip_address + description = "Contains the public IP address of the Azure NAT Gateway." +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/nat_gateway/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/nat_gateway/variables.tf new file mode 100644 index 0000000..c1c8ea5 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/nat_gateway/variables.tf @@ -0,0 +1,43 @@ +variable "resource_group_name" { + description = "(Required) Specifies the name of the resource group." + type = string +} + +variable "location" { + description = "(Required) Specifies the location of the Azure NAT Gateway" + type = string +} + +variable "name" { + description = "(Required) Specifies the name of the Azure NAT Gateway" + type = string +} + +variable "tags" { + description = "(Optional) Specifies the tags of the Azure NAT Gateway" + type = map(any) + default = {} +} + +variable "sku_name" { + description = "(Optional) The SKU which should be used. At this time the only supported value is Standard. Defaults to Standard" + type = string + default = "Standard" +} + +variable "idle_timeout_in_minutes" { + description = "(Optional) The idle timeout which should be used in minutes. Defaults to 4." + type = number + default = 4 +} + +variable "zones" { + description = " (Optional) A list of Availability Zones in which this NAT Gateway should be located. Changing this forces a new NAT Gateway to be created." + type = list(string) + default = [] +} + +variable "subnet_ids" { + description = "(Required) A map of subnet ids to associate with the NAT Gateway" + type = map(string) +} \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/network_security_group/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/network_security_group/main.tf new file mode 100644 index 0000000..c649652 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/network_security_group/main.tf @@ -0,0 +1,53 @@ +resource "azurerm_network_security_group" "example" { + name = var.name + resource_group_name = var.resource_group_name + location = var.location + tags = var.tags + + dynamic "security_rule" { + for_each = try(var.security_rules, []) + content { + name = try(security_rule.value.name, null) + priority = try(security_rule.value.priority, null) + direction = try(security_rule.value.direction, null) + access = try(security_rule.value.access, null) + protocol = try(security_rule.value.protocol, null) + source_port_range = try(security_rule.value.source_port_range, null) + source_port_ranges = try(security_rule.value.source_port_ranges, null) + destination_port_range = try(security_rule.value.destination_port_range, null) + destination_port_ranges = try(security_rule.value.destination_port_ranges, null) + source_address_prefix = try(security_rule.value.source_address_prefix, null) + source_address_prefixes = try(security_rule.value.source_address_prefixes, null) + destination_address_prefix = try(security_rule.value.destination_address_prefix, null) + destination_address_prefixes = try(security_rule.value.destination_address_prefixes, null) + source_application_security_group_ids = try(security_rule.value.source_application_security_group_ids, null) + destination_application_security_group_ids = try(security_rule.value.destination_application_security_group_ids, null) + } + } + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +resource "azurerm_subnet_network_security_group_association" "example" { + for_each = var.subnet_ids + subnet_id = each.value + network_security_group_id = azurerm_network_security_group.example.id +} + +resource "azurerm_monitor_diagnostic_setting" "settings" { + name = "DiagnosticsSettings" + target_resource_id = azurerm_network_security_group.example.id + log_analytics_workspace_id = var.log_analytics_workspace_id + + enabled_log { + category = "NetworkSecurityGroupEvent" + } + + enabled_log { + category = "NetworkSecurityGroupRuleCounter" + } +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/network_security_group/outputs.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/network_security_group/outputs.tf new file mode 100644 index 0000000..b8ca8d5 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/network_security_group/outputs.tf @@ -0,0 +1,9 @@ +output "name" { + description = "Specifies the name of the network security group" + value = azurerm_network_security_group.example.name +} + +output "id" { + description = "Specifies the resource id of the network security group" + value = azurerm_network_security_group.example.id +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/network_security_group/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/network_security_group/variables.tf new file mode 100644 index 0000000..04eb07e --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/network_security_group/variables.tf @@ -0,0 +1,51 @@ +variable "name" { + description = "(Required) Specifies the name of the Azure Network Security Group" + type = string +} + +variable "resource_group_name" { + description = "(Required) Specifies the name of the resource group. of the Azure Network Security Group" + type = string +} + +variable "location" { + description = "(Required) Specifies the location of the Azure Network Security Group" + type = string +} + +variable "security_rules" { + description = "(Optional) Specifies the security rules of the Azure Network Security Group" + type = list(object({ + name = string + priority = number + direction = string + access = string + protocol = string + source_port_range = string + source_port_ranges = list(string) + destination_port_range = string + destination_port_ranges = list(string) + source_address_prefix = string + source_address_prefixes = list(string) + destination_address_prefix = string + destination_address_prefixes = list(string) + source_application_security_group_ids = list(string) + destination_application_security_group_ids = list(string) + })) + default = [] +} + +variable "subnet_ids" { + description = "(Required) A map of subnet ids to associate with the Azure Network Security Group" + type = map(string) +} + +variable "tags" { + description = "(Optional) Specifies the tags of the Azure Network Security Group" + default = {} +} + +variable "log_analytics_workspace_id" { + description = "Specifies the resource id of the Azure Log Analytics workspace" + type = string +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_dns_zone/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_dns_zone/main.tf new file mode 100644 index 0000000..393f9dc --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_dns_zone/main.tf @@ -0,0 +1,26 @@ +resource "azurerm_private_dns_zone" "example" { + name = var.name + resource_group_name = var.resource_group_name + tags = var.tags + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +resource "azurerm_private_dns_zone_virtual_network_link" "example" { + for_each = var.virtual_networks_to_link + + name = "link_to_${lower(basename(each.key))}" + resource_group_name = var.resource_group_name + private_dns_zone_name = azurerm_private_dns_zone.example.name + virtual_network_id = "/subscriptions/${each.value.subscription_id}/resourceGroups/${each.value.resource_group_name}/providers/Microsoft.Network/virtualNetworks/${each.key}" + + lifecycle { + ignore_changes = [ + tags + ] + } +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_dns_zone/outputs.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_dns_zone/outputs.tf new file mode 100644 index 0000000..ca141f3 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_dns_zone/outputs.tf @@ -0,0 +1,9 @@ +output "name" { + description = "Specifies the name of the private dns zone" + value = azurerm_private_dns_zone.example.name +} + +output "id" { + description = "Specifies the resource id of the private dns zone" + value = azurerm_private_dns_zone.example.id +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_dns_zone/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_dns_zone/variables.tf new file mode 100644 index 0000000..8d0c0cc --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_dns_zone/variables.tf @@ -0,0 +1,20 @@ +variable "name" { + description = "(Required) Specifies the name of the Azure Private DNS Zone" + type = string +} + +variable "resource_group_name" { + description = "(Required) Specifies the name of the resource group. of the Azure Private DNS Zone" + type = string +} + +variable "tags" { + description = "(Optional) Specifies the tags of the Azure Private DNS Zone" + default = {} +} + +variable "virtual_networks_to_link" { + description = "(Optional) Specifies the subscription id, resource group name, and name of the virtual networks to which create a virtual network link" + type = map(any) + default = {} +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_endpoint/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_endpoint/main.tf new file mode 100644 index 0000000..62bfbfb --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_endpoint/main.tf @@ -0,0 +1,26 @@ +resource "azurerm_private_endpoint" "example" { + name = var.name + location = var.location + resource_group_name = var.resource_group_name + subnet_id = var.subnet_id + tags = var.tags + + private_service_connection { + name = "${var.name}Connection" + private_connection_resource_id = var.private_connection_resource_id + is_manual_connection = var.is_manual_connection + subresource_names = try([var.subresource_name], null) + request_message = try(var.request_message, null) + } + + private_dns_zone_group { + name = var.private_dns_zone_group_name + private_dns_zone_ids = var.private_dns_zone_group_ids + } + + lifecycle { + ignore_changes = [ + tags + ] + } +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_endpoint/outputs.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_endpoint/outputs.tf new file mode 100644 index 0000000..367ab51 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_endpoint/outputs.tf @@ -0,0 +1,19 @@ +output "name" { + description = "Specifies the name of the private endpoint." + value = azurerm_private_endpoint.example.name +} + +output "id" { + description = "Specifies the resource id of the private endpoint." + value = azurerm_private_endpoint.example.id +} + +output "private_dns_zone_group" { + description = "Specifies the private dns zone group of the private endpoint." + value = azurerm_private_endpoint.example.private_dns_zone_group +} + +output "private_dns_zone_configs" { + description = "Specifies the private dns zone(s) configuration" + value = azurerm_private_endpoint.example.private_dns_zone_configs +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_endpoint/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_endpoint/variables.tf new file mode 100644 index 0000000..2b7a888 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/private_endpoint/variables.tf @@ -0,0 +1,61 @@ +variable "name" { + description = "(Required) Specifies the name of the Azure Private Endpoint. Changing this forces a new resource to be created." + type = string +} + +variable "resource_group_name" { + description = "(Required) The name of the resource group. Changing this forces a new resource to be created." + type = string +} + +variable "private_connection_resource_id" { + description = "(Required) Specifies the resource id of the private link service" + type = string +} + +variable "location" { + description = "(Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created." + type = string +} + +variable "subnet_id" { + description = "(Required) Specifies the resource id of the subnet" + type = string +} + +variable "is_manual_connection" { + description = "(Optional) Specifies whether the Azure Private Endpoint connection requires manual approval from the remote resource owner." + type = string + default = false +} + +variable "subresource_name" { + description = "(Optional) Specifies a subresource name which the Azure Private Endpoint is able to connect to." + type = string + default = null +} + +variable "request_message" { + description = "(Optional) Specifies a message passed to the owner of the remote resource when the Azure Private Endpoint attempts to establish the connection to the remote resource." + type = string + default = null +} + +variable "private_dns_zone_group_name" { + description = "(Required) Specifies the Name of the Private DNS Zone Group. Changing this forces a new private_dns_zone_group resource to be created." + type = string +} + +variable "private_dns_zone_group_ids" { + description = "(Required) Specifies the list of Private DNS Zones to include within the private_dns_zone_group." + type = list(string) +} + +variable "tags" { + description = "(Optional) Specifies the tags of the Azure Azure Private Endpoint." + default = {} +} + +variable "private_dns" { + default = {} +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/storage_account/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/storage_account/main.tf new file mode 100644 index 0000000..27fb626 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/storage_account/main.tf @@ -0,0 +1,29 @@ +resource "azurerm_storage_account" "example" { + name = var.name + resource_group_name = var.resource_group_name + + location = var.location + account_kind = var.account_kind + account_tier = var.account_tier + account_replication_type = var.replication_type + is_hns_enabled = var.is_hns_enabled + shared_access_key_enabled = var.shared_access_key_enabled + tags = var.tags + + network_rules { + default_action = (length(var.ip_rules) + length(var.virtual_network_subnet_ids)) > 0 ? "Deny" : var.default_action + ip_rules = var.ip_rules + virtual_network_subnet_ids = var.virtual_network_subnet_ids + bypass = var.bypass + } + + identity { + type = "SystemAssigned" + } + + lifecycle { + ignore_changes = [ + tags + ] + } +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/storage_account/outputs.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/storage_account/outputs.tf new file mode 100644 index 0000000..02d32ec --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/storage_account/outputs.tf @@ -0,0 +1,24 @@ +output "name" { + description = "Specifies the name of the storage account" + value = azurerm_storage_account.example.name +} + +output "id" { + description = "Specifies the resource id of the storage account" + value = azurerm_storage_account.example.id +} + +output "primary_access_key" { + description = "Specifies the primary access key of the storage account" + value = azurerm_storage_account.example.primary_access_key +} + +output "principal_id" { + description = "Specifies the principal id of the system assigned managed identity of the storage account" + value = azurerm_storage_account.example.identity[0].principal_id +} + +output "primary_blob_endpoint" { + description = "Specifies the primary blob endpoint of the storage account" + value = azurerm_storage_account.example.primary_blob_endpoint +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/storage_account/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/storage_account/variables.tf new file mode 100644 index 0000000..d1e61d7 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/storage_account/variables.tf @@ -0,0 +1,93 @@ +variable "resource_group_name" { + description = "(Required) Specifies the name of the resource group. of the Azure Storage Account" + type = string +} + +variable "name" { + description = "(Required) Specifies the name of the Azure Storage Account" + type = string +} + +variable "location" { + description = "(Required) Specifies the location of the Azure Storage Account" + type = string +} + +variable "account_kind" { + description = "(Optional) Specifies the account kind of the Azure Storage Account" + default = "StorageV2" + type = string + + validation { + condition = contains(["Storage", "StorageV2"], var.account_kind) + error_message = "The account kind of the Azure Storage Account is invalid." + } +} + +variable "account_tier" { + description = "(Optional) Specifies the account tier of the Azure Storage Account" + default = "Standard" + type = string + + validation { + condition = contains(["Standard", "Premium"], var.account_tier) + error_message = "The account tier of the Azure Storage Account is invalid." + } +} + +variable "replication_type" { + description = "(Optional) Specifies the replication type of the Azure Storage Account" + default = "LRS" + type = string + + validation { + condition = contains(["LRS", "ZRS", "GRS", "GZRS", "RA-GRS", "RA-GZRS"], var.replication_type) + error_message = "The replication type of the Azure Storage Account is invalid." + } +} + +variable "is_hns_enabled" { + description = "(Optional) Specifies the replication type of the Azure Storage Account" + default = false + type = bool +} + +variable "default_action" { + description = "Allow or disallow public access to all blobs or containers in the Azure Storage Accounts. The default interpretation is true for this property." + default = "Allow" + type = string +} + +variable "ip_rules" { + description = "Specifies IP rules for the Azure Storage Account" + default = [] + type = list(string) +} + +variable "virtual_network_subnet_ids" { + description = "Specifies a list of resource ids for subnets" + default = [] + type = list(string) +} + +variable "kind" { + description = "(Optional) Specifies the kind of the Azure Storage Account" + default = "" +} + +variable "bypass" { + description = " (Optional) Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Valid options are any combination of Logging, Metrics, AzureServices, or None." + default = ["Logging", "Metrics", "AzureServices"] + type = set(string) +} + +variable "shared_access_key_enabled" { + description = "(Optional) Indicates whether the storage account permits requests to be authorized with the account access key via Shared Key. If false, then all requests, including shared access signatures, must be authorized with Azure Active Directory (Azure AD). Defaults to true." + default = true + type = bool +} + +variable "tags" { + description = "(Optional) Specifies the tags of the Azure Storage Account" + default = {} +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/virtual_network/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/virtual_network/main.tf new file mode 100644 index 0000000..cec00f4 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/virtual_network/main.tf @@ -0,0 +1,55 @@ +resource "azurerm_virtual_network" "example" { + name = var.vnet_name + address_space = var.address_space + location = var.location + resource_group_name = var.resource_group_name + tags = var.tags + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +resource "azurerm_subnet" "example" { + for_each = { for subnet in var.subnets : subnet.name => subnet if subnet != null } + + name = each.key + resource_group_name = var.resource_group_name + virtual_network_name = azurerm_virtual_network.example.name + address_prefixes = each.value.address_prefixes + private_endpoint_network_policies = each.value.private_endpoint_network_policies + private_link_service_network_policies_enabled = each.value.private_link_service_network_policies_enabled + + dynamic "delegation" { + for_each = each.value.delegation != null ? [each.value.delegation] : [] + content { + name = "delegation" + + service_delegation { + name = delegation.value + } + } + } + + lifecycle { + ignore_changes = [ + delegation + ] + } +} + +resource "azurerm_monitor_diagnostic_setting" "example" { + name = "DiagnosticsSettings" + target_resource_id = azurerm_virtual_network.example.id + log_analytics_workspace_id = var.log_analytics_workspace_id + + enabled_log { + category = "VMProtectionAlerts" + } + + enabled_metric { + category = "AllMetrics" + } +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/virtual_network/outputs.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/virtual_network/outputs.tf new file mode 100644 index 0000000..b464308 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/virtual_network/outputs.tf @@ -0,0 +1,19 @@ +output "name" { + description = "Specifies the name of the virtual network" + value = azurerm_virtual_network.example.name +} + +output "vnet_id" { + description = "Specifies the resource id of the virtual network" + value = azurerm_virtual_network.example.id +} + +output "subnet_ids" { + description = "Contains a list of the the resource id of the subnets" + value = { for subnet in azurerm_subnet.example : subnet.name => subnet.id } +} + +output "subnet_ids_as_list" { + description = "Returns the list of the subnet ids as a list of strings." + value = [for subnet in azurerm_subnet.example : subnet.id] +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/virtual_network/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/virtual_network/variables.tf new file mode 100644 index 0000000..f8c0b0e --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/virtual_network/variables.tf @@ -0,0 +1,40 @@ +variable "resource_group_name" { + description = "Resource Group name" + type = string +} + +variable "location" { + description = "Location in which to deploy the network" + type = string +} + +variable "vnet_name" { + description = "VNET name" + type = string +} + +variable "address_space" { + description = "VNET address space" + type = list(string) +} + +variable "subnets" { + description = "Subnets configuration" + type = list(object({ + name = string + address_prefixes = list(string) + private_endpoint_network_policies = string + private_link_service_network_policies_enabled = bool + delegation = string + })) +} + +variable "tags" { + description = "(Optional) Specifies the tags of the Azure Virtual Network resource." + default = {} +} + +variable "log_analytics_workspace_id" { + description = "Specifies the resource id of the Azure Log Analytics workspace." + type = string +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/web_app/main.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/web_app/main.tf new file mode 100644 index 0000000..64e9176 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/web_app/main.tf @@ -0,0 +1,81 @@ +resource "azurerm_linux_web_app" "example" { + name = var.name + resource_group_name = var.resource_group_name + location = var.location + service_plan_id = var.service_plan_id + https_only = var.https_only + virtual_network_subnet_id = var.virtual_network_subnet_id + public_network_access_enabled = var.public_network_access_enabled + client_affinity_enabled = false + tags = var.tags + + identity { + type = "SystemAssigned" + } + + site_config { + always_on = var.always_on + http2_enabled = var.http2_enabled + minimum_tls_version = var.minimum_tls_version + vnet_route_all_enabled = var.vnet_route_all_enabled + application_stack { + python_version = var.python_version + } + } + + app_settings = var.app_settings + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +# Deploy code from a public GitHub repo +resource "azurerm_app_service_source_control" "example" { + count = var.repo_url == "" ? 0 : 1 + app_id = azurerm_linux_web_app.example.id + repo_url = var.repo_url + branch = var.repo_branch + use_manual_integration = true + use_mercurial = false +} + +resource "azurerm_monitor_diagnostic_setting" "example" { + name = "DiagnosticsSettings" + target_resource_id = azurerm_linux_web_app.example.id + log_analytics_workspace_id = var.log_analytics_workspace_id + + enabled_log { + category = "AppServiceHTTPLogs" + } + + enabled_log { + category = "AppServiceConsoleLogs" + } + + enabled_log { + category = "AppServiceAppLogs" + } + + enabled_log { + category = "AppServiceAuditLogs" + } + + enabled_log { + category = "AppServiceIPSecAuditLogs" + } + + enabled_log { + category = "AppServicePlatformLogs" + } + + enabled_log { + category = "AppServiceAuthenticationLogs" + } + + enabled_metric { + category = "AllMetrics" + } +} \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/web_app/outputs.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/web_app/outputs.tf new file mode 100644 index 0000000..d7b6981 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/web_app/outputs.tf @@ -0,0 +1,24 @@ +output "id" { + value = azurerm_linux_web_app.example.id + description = "Specifies the resource id of the Web App" +} + +output "name" { + value = azurerm_linux_web_app.example.name + description = "Specifies the name of the Web App" +} + +output "default_hostname" { + value = azurerm_linux_web_app.example.default_hostname + description = "Specifies the default hostname of the Web App" +} + +output "outbound_ip_addresses" { + value = azurerm_linux_web_app.example.outbound_ip_addresses + description = "Specifies the outbound IP addresses of the Web App" +} + +output "principal_id" { + value = azurerm_linux_web_app.example.identity[0].principal_id + description = "Specifies the Principal ID of the System Assigned Managed Identity" +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/web_app/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/web_app/variables.tf new file mode 100644 index 0000000..a1ea892 --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/modules/web_app/variables.tf @@ -0,0 +1,96 @@ +variable "resource_group_name" { + description = "(Required) Specifies the name of the resource group." + type = string +} + +variable "location" { + description = "(Required) Specifies the location for the Web App." + type = string +} + +variable "name" { + description = "(Required) Specifies the name of the Web App." + type = string +} + +variable "service_plan_id" { + description = "(Required) Specifies the ID of the App Service Plan within which to create this Web App." + type = string +} + +variable "https_only" { + description = "(Optional) Specifies whether the Web App requires HTTPS connections." + type = bool + default = false +} + +variable "virtual_network_subnet_id" { + description = "(Optional) The subnet id which will be used by this Web App for regional virtual network integration." + type = string + default = null +} + +variable "vnet_route_all_enabled" { + description = "(Optional) Specifies whether to route all traffic from the Web App into the virtual network. This is only applicable if virtual_network_subnet_id is specified. Defaults to false." + type = bool + default = false +} + +variable "public_network_access_enabled" { + description = "(Optional) Specifies whether the public network access is enabled or disabled." + type = bool + default = true +} + +variable "always_on" { + description = "(Optional) Specifies whether the Web App is Always On enabled." + type = bool + default = true +} + +variable "http2_enabled" { + description = "(Optional) Specifies whether HTTP/2 is enabled for the Web App." + type = bool + default = false +} + +variable "minimum_tls_version" { + description = "(Optional) Specifies the minimum version of TLS required for SSL requests." + type = string + default = "1.2" +} + +variable "python_version" { + description = "(Optional) Specifies the version of Python to run." + type = string + default = "3.12" +} + +variable "app_settings" { + description = "(Optional) A map of key-value pairs for App Settings." + type = map(string) + default = {} +} + +variable "repo_url" { + description = "(Optional) Specifies the Git repository URL." + type = string + default = "" +} + +variable "repo_branch" { + description = "(Optional) Specifies the Git repository branch." + type = string + default = "main" +} + +variable "tags" { + description = "(Optional) Specifies the tags to be applied to the resources." + type = map(any) + default = {} +} + +variable "log_analytics_workspace_id" { + description = "Specifies the resource id of the Azure Log Analytics workspace." + type = string +} diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/outputs.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/outputs.tf index f527142..0faff98 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/terraform/outputs.tf +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/outputs.tf @@ -3,21 +3,21 @@ output "resource_group_name" { } output "cosmosdb_account_name" { - value = azurerm_cosmosdb_account.example.name + value = module.cosmosdb_mongodb.name } output "cosmosdb_document_endpoint" { - value = azurerm_cosmosdb_account.example.endpoint + value = module.cosmosdb_mongodb.endpoint } output "app_service_plan_name" { - value = azurerm_service_plan.example.name + value = module.app_service_plan.name } output "web_app_name" { - value = azurerm_linux_web_app.example.name + value = module.web_app.name } output "web_app_url" { - value = azurerm_linux_web_app.example.default_hostname + value = module.web_app.default_hostname } \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/providers.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/providers.tf index 566cfc6..7406526 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/terraform/providers.tf +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/providers.tf @@ -1,10 +1,8 @@ terraform { - required_version = ">=1.0" - required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=4.14.0" + version = "=4.60.0" } } } @@ -16,11 +14,11 @@ provider "azurerm" { } } - # Set the hostname of the Azure Metadata Service (for example management.azure.com) - # used to obtain the Cloud Environment when using LocalStack's Azure emulator. - # This allows the provider to correctly identify the environment and avoid making calls to the real Azure endpoints. + # Set the hostname of the Azure Metadata Service (for example management.azure.com) + # used to obtain the Cloud Environment when using LocalStack's Azure emulator. + # This allows the provider to correctly identify the environment and avoid making calls to the real Azure endpoints. metadata_host="localhost.localstack.cloud:4566" # Set the subscription ID to a dummy value when using LocalStack's Azure emulator. subscription_id = "00000000-0000-0000-0000-000000000000" -} +} \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/terraform.tfvars b/samples/web-app-cosmosdb-mongodb-api/python/terraform/terraform.tfvars deleted file mode 100644 index 6b3badf..0000000 --- a/samples/web-app-cosmosdb-mongodb-api/python/terraform/terraform.tfvars +++ /dev/null @@ -1,3 +0,0 @@ -location = "westeurope" -cosmosdb_database_name = "sampledb" -cosmosdb_collection_name = "activities" \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/terraforom.tfvars b/samples/web-app-cosmosdb-mongodb-api/python/terraform/terraforom.tfvars new file mode 100644 index 0000000..919af4f --- /dev/null +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/terraforom.tfvars @@ -0,0 +1,3 @@ +prefix = "local" +suffix = "test" +location = "westeurope" \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/terraform/variables.tf b/samples/web-app-cosmosdb-mongodb-api/python/terraform/variables.tf index a8842cc..702e90d 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/terraform/variables.tf +++ b/samples/web-app-cosmosdb-mongodb-api/python/terraform/variables.tf @@ -1,7 +1,7 @@ variable "prefix" { description = "(Optional) Specifies the prefix for the name of the Azure resources." type = string - default = "webdb" + default = "local" validation { condition = var.prefix == null || length(var.prefix) >= 2 @@ -23,7 +23,7 @@ variable "suffix" { variable "location" { description = "(Required) Specifies the location for all resources." type = string - default = null + default = "westeurope" } variable "primary_region" { @@ -58,6 +58,12 @@ variable "mongodb_server_version" { } } +variable "database_throughput" { + description = "(Optional) Specifies the throughput for the MongoDB database." + type = number + default = 400 +} + variable "consistency_level" { description = "(Required) Specifies the consistency level for the Azure Cosmos DB account." type = string @@ -242,4 +248,70 @@ variable "tags" { environment = "test" iac = "terraform" } +} + +variable "vnet_name" { + description = "Specifies the name of the virtual network." + default = "VNet" + type = string +} + +variable "vnet_address_space" { + description = "Specifies the address space of the virtual network." + default = ["10.0.0.0/8"] + type = list(string) +} + +variable "webapp_subnet_name" { + description = "Specifies the name of the web app subnet." + default = "app-subnet" + type = string +} + +variable "webapp_subnet_address_prefix" { + description = "Specifies the address prefix of the web app subnet." + default = ["10.0.0.0/24"] + type = list(string) +} + +variable "pe_subnet_name" { + description = "Specifies the name of the subnet that contains the private endpoints." + default = "pe-subnet" + type = string +} + +variable "pe_subnet_address_prefix" { + description = "Specifies the address prefix of the subnet that contains the private endpoints." + default = ["10.0.1.0/24"] + type = list(string) +} + +variable "nat_gateway_name" { + description = "(Required) Specifies the name of the NAT Gateway" + type = string + default = "NatGateway" +} + +variable "nat_gateway_sku_name" { + description = "(Optional) The SKU which should be used. At this time the only supported value is Standard. Defaults to Standard" + type = string + default = "Standard" +} + +variable "nat_gateway_idle_timeout_in_minutes" { + description = "(Optional) The idle timeout which should be used in minutes. Defaults to 4." + type = number + default = 4 +} + +variable "nat_gateway_zones" { + description = " (Optional) A list of Availability Zones in which this NAT Gateway should be located. Changing this forces a new NAT Gateway to be created." + type = list(string) + default = ["1"] +} + +variable "website_port" { + description = "(Optional) Specifies the port on which the Web App will listen. Defaults to 8000." + type = number + default = 8000 } \ No newline at end of file diff --git a/samples/web-app-cosmosdb-mongodb-api/python/visio/architecture.vsdx b/samples/web-app-cosmosdb-mongodb-api/python/visio/architecture.vsdx index 2572444..ea0bb4d 100644 Binary files a/samples/web-app-cosmosdb-mongodb-api/python/visio/architecture.vsdx and b/samples/web-app-cosmosdb-mongodb-api/python/visio/architecture.vsdx differ diff --git a/samples/web-app-sql-database/python/scripts/get-web-app-url.sh b/samples/web-app-sql-database/python/scripts/get-web-app-url.sh old mode 100644 new mode 100755 diff --git a/samples/web-app-sql-database/python/scripts/validate.sh b/samples/web-app-sql-database/python/scripts/validate.sh old mode 100644 new mode 100755