From 696e84ce4cadec025c9f15160540086e99eedc9c Mon Sep 17 00:00:00 2001 From: Timna Brown <24630902+brown9804@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:54:15 -0600 Subject: [PATCH 1/8] Create README for Azure Terraform templates Added detailed instructions and structure for Azure infrastructure setup using Terraform. --- terraform-infrastructure/README.md | 121 +++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 terraform-infrastructure/README.md diff --git a/terraform-infrastructure/README.md b/terraform-infrastructure/README.md new file mode 100644 index 0000000..fc1c5ea --- /dev/null +++ b/terraform-infrastructure/README.md @@ -0,0 +1,121 @@ +# Azure Infrastructure Terraform Templates + +Costa Rica + +[![GitHub](https://img.shields.io/badge/--181717?logo=github&logoColor=ffffff)](https://github.com/) +[brown9804](https://github.com/brown9804) + +Last updated: 2025-11-03 + +---------- + +> This approach focuses on `setting up the required infrastructure via Terraform`. It allows for source control of not only the solution code, connections, and setups `but also the infrastructure itself`. + +
+ Centered Image +
+ +## Prerequisites + +- An `Azure subscription is required`. All other resources, including instructions for creating a Resource Group, are provided in this workshop. +- `Contributor role assigned or any custom role that allows`: access to manage all resources, and the ability to deploy resources within subscription. +- Please ensure that: + - [Terraform is installed on your local machine](https://developer.hashicorp.com/terraform/tutorials/azure-get-started/install-cli#install-terraform). + - [Install the Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) to work with both Terraform and Azure commands. + +## Overview + +Templates structure: + +``` +. +├── README.md +├────── main.tf +├────── variables.tf +├────── provider.tf +├────── terraform.tfvars +├────── outputs.tf +``` + +- main.tf `(Main Terraform configuration file)`: This file contains the core infrastructure code. It defines the resources you want to create, such as virtual machines, networks, and storage. It's the primary file where you describe your infrastructure in a declarative manner. +- variables.tf `(Variable definitions)`: This file is used to define variables that can be used throughout your Terraform configuration. By using variables, you can make your configuration more flexible and reusable. For example, you can define variables for resource names, sizes, and other parameters that might change between environments. +- provider.tf `(Provider configurations)`: Providers are plugins that Terraform uses to interact with cloud providers, SaaS providers, and other APIs. This file specifies which providers (e.g., AWS, Azure, Google Cloud) you are using and any necessary configuration for them, such as authentication details. +- terraform.tfvars `(Variable values)`: This file contains the actual values for the variables defined in `variables.tf`. By separating variable definitions and values, you can easily switch between different sets of values for different environments (e.g., development, staging, production) without changing the main configuration files. +- outputs.tf `(Output values)`: This file defines the output values that Terraform should return after applying the configuration. Outputs are useful for displaying information about the resources created, such as IP addresses, resource IDs, and other important details. They can also be used as inputs for other Terraform configurations or scripts. + +## How to execute it + +```mermaid +graph TD; + A[az login] --> B(terraform init) + B --> C{Terraform provisioning stage} + C -->|Review| D[terraform plan] + C -->|Order Now| E[terraform apply] + C -->|Delete Resource if needed| F[terraform destroy] +``` + +> [!IMPORTANT] +> Please modify `terraform.tfvars` with your information, then run the following flow. If you need more visual guidance, please check the video that illustrates the provisioning steps. + +1. **Login to Azure**: This command logs you into your Azure account. It opens a browser window where you can enter your Azure credentials. Once logged in, you can manage your Azure resources from the command line. + + > Go to the path where Terraform files are located: + + ```sh + cd terraform-infrastructure + ``` + + ```sh + az login + ``` + + img + + img + +2. **Initialize Terraform**: Initializes the working directory containing the Terraform configuration files. It downloads the necessary provider plugins and sets up the backend for storing the state. + + ``` sh + terraform init + ``` + + img + +3. **Terraform Provisioning Stage**: + + - **Review**: Creates an execution plan, showing what actions Terraform will take to achieve the desired state defined in your configuration files. It uses the variable values specified in `terraform.tfvars`. + + ```sh + terraform plan -var-file terraform.tfvars + ``` + + > At the end, you will see a message in green if everything was executed successfully: + + Screenshot 2025-03-18 145143 + + - **Order Now**: Applies the changes required to reach the desired state of the configuration. It prompts for confirmation before making any changes. It also uses the variable values specified in `terraform.tfvars`. + + ```sh + terraform apply -var-file terraform.tfvars + ``` + + > At the end, you will see a message in green if everything was executed successfully: + + image + + - **Remove**: Destroys the infrastructure managed by Terraform. It prompts for confirmation before deleting any resources. It also uses the variable values specified in `terraform.tfvars`. + + ```sh + terraform destroy -var-file terraform.tfvars + ``` + + > At the end, you will see a message in green if everything was executed successfully: + + image + + +
+ Total views +

Refresh Date: 2025-11-12

+
+ From f77e76785ae06770887cfdccc91e3e0a75d3bbef Mon Sep 17 00:00:00 2001 From: Timna Brown <24630902+brown9804@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:27:41 -0600 Subject: [PATCH 2/8] resouce overview Updated the image source in the README to a valid URL. --- terraform-infrastructure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform-infrastructure/README.md b/terraform-infrastructure/README.md index fc1c5ea..1932dcd 100644 --- a/terraform-infrastructure/README.md +++ b/terraform-infrastructure/README.md @@ -12,7 +12,7 @@ Last updated: 2025-11-03 > This approach focuses on `setting up the required infrastructure via Terraform`. It allows for source control of not only the solution code, connections, and setups `but also the infrastructure itself`.
- Centered Image + Centered Image
## Prerequisites From 553923fcf996695d54d0354dbad178f65c3e8115 Mon Sep 17 00:00:00 2001 From: Timna Brown <24630902+brown9804@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:28:02 -0600 Subject: [PATCH 3/8] Add Terraform configuration for Azure resources This Terraform configuration sets up various Azure resources including a resource group, Cosmos DB account, storage account, AI Foundry account, and role assignments for service principals. It also includes configuration for a web app and its associated services. --- terraform-infrastructure/main.tf | 280 +++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 terraform-infrastructure/main.tf diff --git a/terraform-infrastructure/main.tf b/terraform-infrastructure/main.tf new file mode 100644 index 0000000..2e7dd37 --- /dev/null +++ b/terraform-infrastructure/main.tf @@ -0,0 +1,280 @@ + +# Create resource group if it does not exist +resource "azurerm_resource_group" "rg" { + name = var.resource_group_name + location = var.location +} + +# Subscription context for role assignments +data "azurerm_client_config" "current" {} + +# Random suffix to mimic uniqueString(resourceGroup().id) +resource "random_id" "suffix" { + byte_length = 4 +} + +locals { + # Use provided user_principal_id or default to current Azure CLI user + principal_id = var.user_principal_id != null ? var.user_principal_id : data.azurerm_client_config.current.object_id + suffix = substr(random_id.suffix.hex, 0, 8) + cosmos_account_name = "${var.name_prefix}${local.suffix}cosmosdb" + cosmos_db_name = "zava" + storage_account = lower(replace("${var.name_prefix}${local.suffix}sa", "-", "")) + ai_foundry_name = "aif-${local.suffix}" # custom subdomain + ai_project_name = "proj-${local.suffix}" + search_service_name = "${var.name_prefix}-${local.suffix}-search" + app_service_plan = "${var.name_prefix}-${local.suffix}-asp" + log_analytics_name = "${var.name_prefix}-${local.suffix}-la" + app_insights_name = "${var.name_prefix}-${local.suffix}-ai" + registry_name = lower(replace("${var.name_prefix}${local.suffix}cosureg", "-", "")) + web_app_name = "${var.name_prefix}-${local.suffix}-app" +} + +resource "azurerm_cosmosdb_account" "cosmos" { + name = local.cosmos_account_name + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + offer_type = "Standard" + kind = "GlobalDocumentDB" + consistency_policy { + consistency_level = "Session" + max_interval_in_seconds = 5 + max_staleness_prefix = 100 + } + geo_location { + location = var.location + failover_priority = 0 + } + free_tier_enabled = false + analytical_storage_enabled = false + local_authentication_disabled = !var.enable_cosmos_local_auth +} + +resource "azurerm_cosmosdb_sql_database" "cosmosdb" { + name = local.cosmos_db_name + resource_group_name = azurerm_resource_group.rg.name + account_name = azurerm_cosmosdb_account.cosmos.name + throughput = 400 +} + +# Storage account using AzAPI to bypass policy restrictions +resource "azapi_resource" "storage" { + type = "Microsoft.Storage/storageAccounts@2023-01-01" + name = local.storage_account + location = var.location + parent_id = azurerm_resource_group.rg.id + + body = jsonencode({ + sku = { + name = "Standard_LRS" + } + kind = "StorageV2" + properties = { + accessTier = "Hot" + allowSharedKeyAccess = true + defaultToOAuthAuthentication = false + allowBlobPublicAccess = false + minimumTlsVersion = "TLS1_2" + supportsHttpsTrafficOnly = true + } + }) + + identity { + type = "SystemAssigned" + } +} + +# AI Foundry account (preview) using AzAPI provider. +resource "azapi_resource" "ai_foundry" { + type = "Microsoft.CognitiveServices/accounts@2025-06-01" + name = local.ai_foundry_name + location = var.location + parent_id = azurerm_resource_group.rg.id + schema_validation_enabled = false + identity { type = "SystemAssigned" } + body = jsonencode({ + sku = { name = "S0" } + kind = "AIServices" + properties = { + allowProjectManagement = true + customSubDomainName = local.ai_foundry_name + disableLocalAuth = false + } + }) +} + +resource "azapi_resource" "ai_project" { + type = "Microsoft.CognitiveServices/accounts/projects@2025-06-01" + name = local.ai_project_name + location = var.location + parent_id = azapi_resource.ai_foundry.id + schema_validation_enabled = false + identity { type = "SystemAssigned" } + body = jsonencode({ properties = {} }) + depends_on = [azapi_resource.ai_foundry] +} + +resource "azurerm_search_service" "search" { + name = local.search_service_name + resource_group_name = azurerm_resource_group.rg.name + location = var.location + sku = "standard" + identity { type = "SystemAssigned" } +} + +resource "azurerm_log_analytics_workspace" "law" { + name = local.log_analytics_name + location = var.location + resource_group_name = azurerm_resource_group.rg.name + sku = "PerGB2018" + retention_in_days = 90 + daily_quota_gb = 1 +} + +resource "azurerm_application_insights" "appinsights" { + name = local.app_insights_name + location = var.location + resource_group_name = azurerm_resource_group.rg.name + application_type = "web" + workspace_id = azurerm_log_analytics_workspace.law.id +} + +resource "azurerm_container_registry" "acr" { + name = local.registry_name + resource_group_name = azurerm_resource_group.rg.name + location = var.location + sku = "Standard" + admin_enabled = true +} + +resource "azurerm_container_registry_webhook" "webhook" { + name = "${local.registry_name}webhook" + resource_group_name = azurerm_resource_group.rg.name + registry_name = azurerm_container_registry.acr.name + location = var.location + + service_uri = "https://${local.web_app_name}.scm.azurewebsites.net/docker/hook" + status = "enabled" + scope = "${local.suffix}/techworkshopl300/zava:latest" + actions = ["push"] + + custom_headers = { + "Content-Type" = "application/json" + } + + depends_on = [azurerm_container_registry.acr] +} + +resource "azurerm_service_plan" "appserviceplan" { + name = local.app_service_plan + resource_group_name = azurerm_resource_group.rg.name + location = var.location + os_type = "Linux" + sku_name = "S1" +} + +resource "azurerm_linux_web_app" "app" { + name = local.web_app_name + resource_group_name = azurerm_resource_group.rg.name + location = var.location + service_plan_id = azurerm_service_plan.appserviceplan.id + https_only = true + + site_config { + application_stack { + docker_image_name = "${local.registry_name}.azurecr.io/${local.suffix}/techworkshopl300/zava:latest" + docker_registry_url = "https://${local.registry_name}.azurecr.io" + } + http2_enabled = true + minimum_tls_version = "1.2" + } + + app_settings = { + WEBSITES_ENABLE_APP_SERVICE_STORAGE = "false" + DOCKER_REGISTRY_SERVER_URL = "https://${local.registry_name}.azurecr.io" + DOCKER_REGISTRY_SERVER_USERNAME = azurerm_container_registry.acr.name + DOCKER_REGISTRY_SERVER_PASSWORD = azurerm_container_registry.acr.admin_password + APPINSIGHTS_INSTRUMENTATIONKEY = azurerm_application_insights.appinsights.instrumentation_key + } + + depends_on = [azurerm_container_registry.acr] +} + +# Cosmos DB SQL Role Assignments (data plane) using AzAPI +locals { + cosmos_db_data_reader_role_id = "00000000-0000-0000-0000-000000000001" + cosmos_db_data_contributor_role_id = "00000000-0000-0000-0000-000000000002" + cosmos_account_reader_role_id = "fbdf93bf-df7d-467e-a4d2-9458aa1360c8" + cognitive_openai_user_role_id = "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd" + cognitive_contributor_role_id = "25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68" +} + +# Assign Cosmos DB Built-in Data Contributor role to specified user principal +resource "azapi_resource" "cosmos_user_data_contributor" { + type = "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-04-15" + name = md5("${azurerm_cosmosdb_account.cosmos.id}-${local.principal_id}-${local.cosmos_db_data_contributor_role_id}") + parent_id = azurerm_cosmosdb_account.cosmos.id + body = jsonencode({ + properties = { + roleDefinitionId = "${azurerm_cosmosdb_account.cosmos.id}/sqlRoleDefinitions/${local.cosmos_db_data_contributor_role_id}" + principalId = local.principal_id + scope = azurerm_cosmosdb_account.cosmos.id + } + }) +} + +# Role assignments for Search managed identity +resource "azurerm_role_assignment" "search_cosmos_account_reader" { + scope = azurerm_cosmosdb_account.cosmos.id + role_definition_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/${local.cosmos_account_reader_role_id}" + principal_id = azurerm_search_service.search.identity[0].principal_id + principal_type = "ServicePrincipal" +} + +resource "azapi_resource" "search_cosmos_data_reader" { + type = "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-04-15" + name = md5("${azurerm_cosmosdb_account.cosmos.id}-${azurerm_search_service.search.identity[0].principal_id}-${local.cosmos_db_data_reader_role_id}") + parent_id = azurerm_cosmosdb_account.cosmos.id + body = jsonencode({ + properties = { + roleDefinitionId = "${azurerm_cosmosdb_account.cosmos.id}/sqlRoleDefinitions/${local.cosmos_db_data_reader_role_id}" + principalId = azurerm_search_service.search.identity[0].principal_id + scope = azurerm_cosmosdb_account.cosmos.id + } + }) +} + +resource "azapi_resource" "search_cosmos_data_contributor" { + type = "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-04-15" + name = md5("${azurerm_cosmosdb_account.cosmos.id}-${azurerm_search_service.search.identity[0].principal_id}-${local.cosmos_db_data_contributor_role_id}") + parent_id = azurerm_cosmosdb_account.cosmos.id + body = jsonencode({ + properties = { + roleDefinitionId = "${azurerm_cosmosdb_account.cosmos.id}/sqlRoleDefinitions/${local.cosmos_db_data_contributor_role_id}" + principalId = azurerm_search_service.search.identity[0].principal_id + scope = azurerm_cosmosdb_account.cosmos.id + } + }) +} + +# Role assignments for AI Project & AI Foundry +resource "azurerm_role_assignment" "search_project_openai_user" { + scope = azapi_resource.ai_project.id + role_definition_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/${local.cognitive_openai_user_role_id}" + principal_id = azurerm_search_service.search.identity[0].principal_id + principal_type = "ServicePrincipal" +} + +resource "azurerm_role_assignment" "search_foundry_openai_user" { + scope = azapi_resource.ai_foundry.id + role_definition_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/${local.cognitive_openai_user_role_id}" + principal_id = azurerm_search_service.search.identity[0].principal_id + principal_type = "ServicePrincipal" +} + +resource "azurerm_role_assignment" "search_project_contributor" { + scope = azapi_resource.ai_project.id + role_definition_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/${local.cognitive_contributor_role_id}" + principal_id = azurerm_search_service.search.identity[0].principal_id + principal_type = "ServicePrincipal" +} From fbcb34748ed4c860aece922391e9f8195a58e7c4 Mon Sep 17 00:00:00 2001 From: Timna Brown <24630902+brown9804@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:28:42 -0600 Subject: [PATCH 4/8] Define outputs for Azure resources in outputs.tf Added outputs for Cosmos DB, Storage Account, Azure AI Search, Container Registry, and App Service. --- terraform-infrastructure/outputs.tf | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 terraform-infrastructure/outputs.tf diff --git a/terraform-infrastructure/outputs.tf b/terraform-infrastructure/outputs.tf new file mode 100644 index 0000000..b6b82ce --- /dev/null +++ b/terraform-infrastructure/outputs.tf @@ -0,0 +1,29 @@ +output "cosmosDbEndpoint" { + value = azurerm_cosmosdb_account.cosmos.endpoint + description = "Cosmos DB account endpoint" +} + +output "storageAccountName" { + value = azapi_resource.storage.name + description = "Storage account name" +} + +output "searchServiceName" { + value = azurerm_search_service.search.name + description = "Azure AI Search service name" +} + +output "container_registry_name" { + value = azurerm_container_registry.acr.name + description = "Azure Container Registry name" +} + +output "application_name" { + value = azurerm_linux_web_app.app.name + description = "App Service name" +} + +output "application_url" { + value = azurerm_linux_web_app.app.default_hostname + description = "Primary host name for the App Service" +} From e728124ae458cb4337e264eb737f4dc036a9d2fc Mon Sep 17 00:00:00 2001 From: Timna Brown <24630902+brown9804@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:29:03 -0600 Subject: [PATCH 5/8] Add provider configuration for Terraform Define Terraform providers for Azure and random resources. --- terraform-infrastructure/provider.tf | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 terraform-infrastructure/provider.tf diff --git a/terraform-infrastructure/provider.tf b/terraform-infrastructure/provider.tf new file mode 100644 index 0000000..b702c46 --- /dev/null +++ b/terraform-infrastructure/provider.tf @@ -0,0 +1,24 @@ +terraform { + required_version = ">= 1.5.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.100" + } + azapi = { + source = "azure/azapi" + version = "~> 1.12" + } + random = { + source = "hashicorp/random" + version = "~> 3.5" + } + } +} + +provider "azurerm" { + features {} +} + +# AzAPI provider is used for preview/unsupported resources (AI Foundry account & project, Cosmos SQL role assignments). +provider "azapi" {} From 092f1da893619a328c067612338d908f1061db28 Mon Sep 17 00:00:00 2001 From: Timna Brown <24630902+brown9804@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:29:26 -0600 Subject: [PATCH 6/8] Add terraform.tfvars for Azure infrastructure --- terraform-infrastructure/terraform.tfvars | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 terraform-infrastructure/terraform.tfvars diff --git a/terraform-infrastructure/terraform.tfvars b/terraform-infrastructure/terraform.tfvars new file mode 100644 index 0000000..6999f5b --- /dev/null +++ b/terraform-infrastructure/terraform.tfvars @@ -0,0 +1,4 @@ +resource_group_name = "RG-AI-retailw3" +location = "westus2" +name_prefix = "zava" +# user_principal_id is optional - defaults to current Azure CLI user (az login) From 43391942bbfdf373273ec52d5cd5207fa51ce2eb Mon Sep 17 00:00:00 2001 From: Timna Brown <24630902+brown9804@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:29:43 -0600 Subject: [PATCH 7/8] Add Terraform variables for Azure resources Define variables for resource group, location, name prefix, user principal ID, and Cosmos DB local auth. --- terraform-infrastructure/variables.tf | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 terraform-infrastructure/variables.tf diff --git a/terraform-infrastructure/variables.tf b/terraform-infrastructure/variables.tf new file mode 100644 index 0000000..295814a --- /dev/null +++ b/terraform-infrastructure/variables.tf @@ -0,0 +1,28 @@ +variable "resource_group_name" { + type = string + description = "Existing resource group name where resources will be deployed" +} + +variable "location" { + type = string + description = "Azure region for resources" + default = "eastus" +} + +variable "name_prefix" { + type = string + description = "Prefix for all resource names (will append random suffix)" + default = "zava" +} + +variable "user_principal_id" { + type = string + description = "Object ID of the user/principal to grant Cosmos DB data contributor access. Defaults to current Azure CLI user." + default = null +} + +variable "enable_cosmos_local_auth" { + type = bool + description = "Whether to enable local auth on Cosmos DB account" + default = true +} From 1948b94f9a1b19c90855d767f06b66354ffe8eed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Nov 2025 19:30:14 +0000 Subject: [PATCH 8/8] Update visitor count --- metrics.json | 37 ++++++++++++++++++++++++++++++ terraform-infrastructure/README.md | 4 ++-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 metrics.json diff --git a/metrics.json b/metrics.json new file mode 100644 index 0000000..8665541 --- /dev/null +++ b/metrics.json @@ -0,0 +1,37 @@ +[ + { + "date": "2025-07-07", + "count": 330, + "uniques": 20 + }, + { + "date": "2025-07-08", + "count": 159, + "uniques": 6 + }, + { + "date": "2025-07-10", + "count": 482, + "uniques": 1 + }, + { + "date": "2025-07-11", + "count": 170, + "uniques": 4 + }, + { + "date": "2025-07-12", + "count": 7, + "uniques": 1 + }, + { + "date": "2025-07-14", + "count": 130, + "uniques": 2 + }, + { + "date": "2025-07-15", + "count": 2, + "uniques": 1 + } +] \ No newline at end of file diff --git a/terraform-infrastructure/README.md b/terraform-infrastructure/README.md index 1932dcd..06d3243 100644 --- a/terraform-infrastructure/README.md +++ b/terraform-infrastructure/README.md @@ -115,7 +115,7 @@ graph TD;
- Total views -

Refresh Date: 2025-11-12

+ Total views +

Refresh Date: 2025-11-21