Skip to content

Commit 4fd76cc

Browse files
Timna BrownTimna Brown
authored andcommitted
Add terraform infrastructure sample
1 parent e4d66bc commit 4fd76cc

8 files changed

Lines changed: 549 additions & 1 deletion

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ crash.*.log
1313
# password, private keys, and other secrets. These should not be part of version
1414
# control as they are data points which are potentially sensitive and subject
1515
# to change depending on the environment.
16-
*.tfvars
1716
*.tfvars.json
1817

1918
# Ignore override files as they are usually used to override resources locally and so

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Last updated: 2025-07-30
3434

3535
## Overview
3636

37+
> The infrastructure sample for this architecture is available in [terraform-infrastructure](terraform-infrastructure/README.md). It provisions the Azure-side resources for the documented workflow, including the Resource Group, Function App dependencies, monitoring, Logic App, Key Vault, SQL, and the AI service account.
38+
3739

3840
<div align="center">
3941
<img src="https://github.com/user-attachments/assets/48774cdd-ba27-404c-b7fc-a124fd176e2a" alt="Centered Image" style="border: 2px solid #4CAF50; border-radius: 5px; padding: 5px;"/>

terraform-infrastructure/README.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Azure Infrastructure Terraform Templates
2+
3+
Costa Rica
4+
5+
[![GitHub](https://img.shields.io/badge/--181717?logo=github&logoColor=ffffff)](https://github.com/)
6+
[Cloud2BR OSS - Learning Hub](https://github.com/Cloud2BR-MSFTLearningHub)
7+
8+
Last updated: 2026-04-06
9+
10+
----------
11+
12+
> 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`.
13+
14+
## Prerequisites
15+
16+
- An `Azure subscription is required`. All other resources, including instructions for creating a Resource Group, are provided in this workshop.
17+
- `Contributor role assigned or any custom role that allows`: access to manage all resources, and the ability to deploy resources within subscription.
18+
- Please ensure that:
19+
- [Terraform is installed on your local machine](https://developer.hashicorp.com/terraform/tutorials/azure-get-started/install-cli#install-terraform).
20+
- [Install the Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) to work with both Terraform and Azure commands.
21+
22+
## Overview
23+
24+
Templates structure:
25+
26+
```
27+
.
28+
├── README.md
29+
├────── main.tf
30+
├────── variables.tf
31+
├────── provider.tf
32+
├────── terraform.tfvars
33+
├────── outputs.tf
34+
```
35+
36+
- 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.
37+
- 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.
38+
- 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.
39+
- 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.
40+
- 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.
41+
42+
## How to execute it
43+
44+
```mermaid
45+
graph TD;
46+
A[az login] --> B(terraform init)
47+
B --> C{Terraform provisioning stage}
48+
C -->|Review| D[terraform plan]
49+
C -->|Order Now| E[terraform apply]
50+
C -->|Delete Resource if needed| F[terraform destroy]
51+
```
52+
53+
> [!IMPORTANT]
54+
> 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.
55+
56+
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.
57+
58+
> Go to the path where Terraform files are located:
59+
60+
```sh
61+
cd terraform-infrastructure
62+
```
63+
64+
```sh
65+
az login
66+
```
67+
68+
<img width="550" alt="img" src="https://github.com/user-attachments/assets/53b47aa7-134e-4cf7-b0b8-cdebdd0583ed" />
69+
70+
<img width="550" alt="img" src="https://github.com/user-attachments/assets/1d9a247d-3dc9-472f-9305-4e4f0ecb72f1" />
71+
72+
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.
73+
74+
``` sh
75+
terraform init
76+
```
77+
78+
<img width="550" alt="img" src="https://github.com/user-attachments/assets/a7a32891-ad72-423a-a1fe-bdb50925b546" />
79+
80+
3. **Terraform Provisioning Stage**:
81+
82+
- **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`.
83+
84+
```sh
85+
terraform plan -var-file terraform.tfvars
86+
```
87+
88+
> At the end, you will see a message in green if everything was executed successfully:
89+
90+
<img width="550" alt="Screenshot 2025-03-18 145143" src="https://github.com/user-attachments/assets/4741e863-1ccd-4f2a-a0b8-d5d1964bd890" />
91+
92+
- **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`.
93+
94+
```sh
95+
terraform apply -var-file terraform.tfvars
96+
```
97+
98+
> At the end, you will see a message in green if everything was executed successfully:
99+
100+
<img width="550" alt="image" src="https://github.com/user-attachments/assets/2b32b63f-3e9f-46da-a5e9-c39360135251">
101+
102+
- **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`.
103+
104+
```sh
105+
terraform destroy -var-file terraform.tfvars
106+
```
107+
108+
> At the end, you will see a message in green if everything was executed successfully:
109+
110+
<img width="550" alt="image" src="https://github.com/user-attachments/assets/f2089d03-3a3d-431d-b462-8148ef519104">
111+
112+
<!-- START BADGE -->
113+
<div align="center">
114+
<img src="https://img.shields.io/badge/Total%20views-1327-limegreen" alt="Total views">
115+
<p>Refresh Date: 2026-04-06</p>
116+
</div>
117+
<!-- END BADGE -->

terraform-infrastructure/main.tf

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
locals {
2+
prefix = lower(replace("${var.workload_name}-${var.environment}", "_", "-"))
3+
compact_prefix = substr(replace(local.prefix, "-", ""), 0, 16)
4+
tags = merge(var.tags, { environment = var.environment, workload = var.workload_name, managed_by = "terraform" })
5+
resource_group_name = "${local.prefix}-rg"
6+
log_analytics_name = "${local.prefix}-law"
7+
app_insights_name = "${local.prefix}-appi"
8+
key_vault_name = substr("${local.prefix}-kv", 0, 24)
9+
identity_name = "${local.prefix}-mi"
10+
runtime_storage_name = substr("${local.compact_prefix}func", 0, 24)
11+
data_storage_name = substr("${local.compact_prefix}data", 0, 24)
12+
service_plan_name = "${local.prefix}-plan"
13+
function_app_name = "${local.prefix}-func"
14+
sql_server_name = substr("${local.prefix}-sql", 0, 60)
15+
sql_database_name = "${local.prefix}-sqldb"
16+
logic_app_name = "${local.prefix}-logic"
17+
ai_account_name = substr("${local.prefix}-ai", 0, 64)
18+
}
19+
20+
resource "azurerm_resource_group" "resource_group" {
21+
name = local.resource_group_name
22+
location = var.location
23+
tags = local.tags
24+
}
25+
26+
resource "azurerm_log_analytics_workspace" "log_analytics" {
27+
name = local.log_analytics_name
28+
location = var.location
29+
resource_group_name = azurerm_resource_group.resource_group.name
30+
sku = var.log_analytics_sku
31+
retention_in_days = var.log_retention_in_days
32+
tags = local.tags
33+
}
34+
35+
resource "azurerm_application_insights" "application_insights" {
36+
name = local.app_insights_name
37+
location = var.location
38+
resource_group_name = azurerm_resource_group.resource_group.name
39+
workspace_id = azurerm_log_analytics_workspace.log_analytics.id
40+
application_type = "web"
41+
tags = local.tags
42+
}
43+
44+
resource "azurerm_user_assigned_identity" "managed_identity" {
45+
name = local.identity_name
46+
location = var.location
47+
resource_group_name = azurerm_resource_group.resource_group.name
48+
tags = local.tags
49+
}
50+
51+
resource "azurerm_key_vault" "key_vault" {
52+
name = local.key_vault_name
53+
location = var.location
54+
resource_group_name = azurerm_resource_group.resource_group.name
55+
tenant_id = data.azurerm_client_config.current.tenant_id
56+
sku_name = "standard"
57+
soft_delete_retention_days = 7
58+
purge_protection_enabled = false
59+
enable_rbac_authorization = true
60+
public_network_access_enabled = true
61+
tags = local.tags
62+
}
63+
64+
resource "azurerm_storage_account" "storage_runtime" {
65+
name = local.runtime_storage_name
66+
location = var.location
67+
resource_group_name = azurerm_resource_group.resource_group.name
68+
account_tier = "Standard"
69+
account_replication_type = var.storage_replication_type
70+
account_kind = "StorageV2"
71+
min_tls_version = "TLS1_2"
72+
public_network_access_enabled = true
73+
shared_access_key_enabled = true
74+
allow_nested_items_to_be_public = false
75+
tags = local.tags
76+
}
77+
78+
resource "azurerm_storage_account" "storage_data" {
79+
count = var.enable_data_storage_account ? 1 : 0
80+
name = local.data_storage_name
81+
location = var.location
82+
resource_group_name = azurerm_resource_group.resource_group.name
83+
account_tier = "Standard"
84+
account_replication_type = var.storage_replication_type
85+
account_kind = "StorageV2"
86+
min_tls_version = "TLS1_2"
87+
public_network_access_enabled = true
88+
shared_access_key_enabled = true
89+
allow_nested_items_to_be_public = false
90+
tags = local.tags
91+
}
92+
93+
resource "azurerm_storage_container" "raw_recommendations" {
94+
count = var.enable_data_storage_account ? 1 : 0
95+
name = "raw-recommendations"
96+
storage_account_id = azurerm_storage_account.storage_data[0].id
97+
container_access_type = "private"
98+
}
99+
100+
resource "azurerm_storage_container" "enriched_recommendations" {
101+
count = var.enable_data_storage_account ? 1 : 0
102+
name = "enriched-recommendations"
103+
storage_account_id = azurerm_storage_account.storage_data[0].id
104+
container_access_type = "private"
105+
}
106+
107+
resource "azurerm_service_plan" "app_service_plan" {
108+
name = local.service_plan_name
109+
location = var.location
110+
resource_group_name = azurerm_resource_group.resource_group.name
111+
os_type = "Linux"
112+
sku_name = var.function_plan_sku_name
113+
tags = local.tags
114+
}
115+
116+
resource "azurerm_cognitive_account" "ai" {
117+
count = var.enable_ai_service ? 1 : 0
118+
name = local.ai_account_name
119+
location = var.location
120+
resource_group_name = azurerm_resource_group.resource_group.name
121+
kind = var.ai_service_kind
122+
sku_name = var.ai_service_sku_name
123+
custom_subdomain_name = substr(replace(local.ai_account_name, "-", ""), 0, 24)
124+
public_network_access_enabled = true
125+
tags = local.tags
126+
}
127+
128+
resource "azurerm_mssql_server" "sql" {
129+
count = var.enable_sql_backend ? 1 : 0
130+
name = local.sql_server_name
131+
resource_group_name = azurerm_resource_group.resource_group.name
132+
location = var.location
133+
version = "12.0"
134+
administrator_login = var.sql_admin_login
135+
administrator_login_password = var.sql_admin_password
136+
minimum_tls_version = "1.2"
137+
public_network_access_enabled = true
138+
tags = local.tags
139+
}
140+
141+
resource "azurerm_mssql_database" "sql_database" {
142+
count = var.enable_sql_backend ? 1 : 0
143+
name = local.sql_database_name
144+
server_id = azurerm_mssql_server.sql[0].id
145+
sku_name = var.sql_sku_name
146+
tags = local.tags
147+
}
148+
149+
resource "azurerm_linux_function_app" "function_app" {
150+
name = local.function_app_name
151+
location = var.location
152+
resource_group_name = azurerm_resource_group.resource_group.name
153+
service_plan_id = azurerm_service_plan.app_service_plan.id
154+
storage_account_name = azurerm_storage_account.storage_runtime.name
155+
storage_account_access_key = azurerm_storage_account.storage_runtime.primary_access_key
156+
https_only = true
157+
tags = local.tags
158+
159+
identity {
160+
type = "UserAssigned"
161+
identity_ids = [azurerm_user_assigned_identity.managed_identity.id]
162+
}
163+
164+
site_config {
165+
application_insights_connection_string = azurerm_application_insights.application_insights.connection_string
166+
application_insights_key = azurerm_application_insights.application_insights.instrumentation_key
167+
ftps_state = "Disabled"
168+
169+
application_stack {
170+
python_version = var.function_python_version
171+
}
172+
}
173+
174+
app_settings = merge(
175+
{
176+
"AzureWebJobsStorage" = "DefaultEndpointsProtocol=https;AccountName=${azurerm_storage_account.storage_runtime.name};AccountKey=${azurerm_storage_account.storage_runtime.primary_access_key};EndpointSuffix=core.windows.net"
177+
"FUNCTIONS_EXTENSION_VERSION" = "~4"
178+
"FUNCTIONS_WORKER_RUNTIME" = "python"
179+
"WEBSITE_RUN_FROM_PACKAGE" = "1"
180+
"WORKLOAD_NAME" = var.workload_name
181+
"AZURE_CLIENT_ID" = azurerm_user_assigned_identity.managed_identity.client_id
182+
"KEY_VAULT_NAME" = azurerm_key_vault.key_vault.name
183+
"DATA_STORAGE_ACCOUNT_NAME" = var.enable_data_storage_account ? azurerm_storage_account.storage_data[0].name : ""
184+
"SQL_SERVER_FQDN" = var.enable_sql_backend ? azurerm_mssql_server.sql[0].fully_qualified_domain_name : ""
185+
"SQL_DATABASE_NAME" = var.enable_sql_backend ? azurerm_mssql_database.sql_database[0].name : ""
186+
"AI_SERVICE_ENDPOINT" = var.enable_ai_service ? azurerm_cognitive_account.ai[0].endpoint : ""
187+
"APPLICATIONINSIGHTS_CONNECTION_STRING" = azurerm_application_insights.application_insights.connection_string
188+
},
189+
var.function_app_settings
190+
)
191+
}
192+
193+
resource "azurerm_logic_app_workflow" "logic_app" {
194+
count = var.enable_logic_app ? 1 : 0
195+
name = local.logic_app_name
196+
location = var.location
197+
resource_group_name = azurerm_resource_group.resource_group.name
198+
enabled = true
199+
tags = local.tags
200+
}
201+
202+
resource "azurerm_role_assignment" "key_vault_secrets_user" {
203+
scope = azurerm_key_vault.key_vault.id
204+
role_definition_name = "Key Vault Secrets User"
205+
principal_id = azurerm_user_assigned_identity.managed_identity.principal_id
206+
}
207+
208+
resource "azurerm_role_assignment" "data_storage_blob_contributor" {
209+
count = var.enable_data_storage_account ? 1 : 0
210+
scope = azurerm_storage_account.storage_data[0].id
211+
role_definition_name = "Storage Blob Data Contributor"
212+
principal_id = azurerm_user_assigned_identity.managed_identity.principal_id
213+
}

0 commit comments

Comments
 (0)