diff --git a/.github/workflows/tfsec.yml b/.github/workflows/tfsec.yml
new file mode 100644
index 0000000..48c3900
--- /dev/null
+++ b/.github/workflows/tfsec.yml
@@ -0,0 +1,38 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: tfsec
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+ schedule:
+ - cron: '28 14 * * 3'
+
+jobs:
+ tfsec:
+ name: Run tfsec sarif report
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ steps:
+ - name: Clone repo
+ uses: actions/checkout@v3
+
+ - name: Run tfsec
+ uses: aquasecurity/tfsec-sarif-action@9a83b5c3524f825c020e356335855741fd02745f
+ with:
+ sarif_file: tfsec.sarif
+
+ - name: Upload SARIF file
+ uses: github/codeql-action/upload-sarif@v2
+ with:
+ # Path to SARIF file relative to the root of the repository
+ sarif_file: tfsec.sarif
diff --git a/.infracost/pricing.gob b/.infracost/pricing.gob
new file mode 100644
index 0000000..2921e6d
Binary files /dev/null and b/.infracost/pricing.gob differ
diff --git a/.infracost/terraform_modules/manifest-73eb4d77fdade6cec426a59518f5a40f.json b/.infracost/terraform_modules/manifest-73eb4d77fdade6cec426a59518f5a40f.json
new file mode 100644
index 0000000..15fc39b
--- /dev/null
+++ b/.infracost/terraform_modules/manifest-73eb4d77fdade6cec426a59518f5a40f.json
@@ -0,0 +1 @@
+{"Path":"d:\\Projects\\terraform-course\\120_azapi_provider","Version":"2.0","Modules":[]}
\ No newline at end of file
diff --git a/.infracost/terraform_modules/manifest-9a64dbcd150fd1f2eeaa11141b4ca3c4.json b/.infracost/terraform_modules/manifest-9a64dbcd150fd1f2eeaa11141b4ca3c4.json
new file mode 100644
index 0000000..2fbc58c
--- /dev/null
+++ b/.infracost/terraform_modules/manifest-9a64dbcd150fd1f2eeaa11141b4ca3c4.json
@@ -0,0 +1 @@
+{"Path":"d:\\Projects\\terraform-course\\93_import_terraform","Version":"2.0","Modules":[]}
\ No newline at end of file
diff --git a/.infracost/terraform_modules/manifest-c9df5a5e064cb112a7cef4f4fccd5118.json b/.infracost/terraform_modules/manifest-c9df5a5e064cb112a7cef4f4fccd5118.json
new file mode 100644
index 0000000..0b16dc7
--- /dev/null
+++ b/.infracost/terraform_modules/manifest-c9df5a5e064cb112a7cef4f4fccd5118.json
@@ -0,0 +1 @@
+{"Path":"d:\\Projects\\terraform-course\\121_appservice_domain","Version":"2.0","Modules":[]}
\ No newline at end of file
diff --git a/07_kubernetes_aks/main.tf b/07_kubernetes_aks/main.tf
index f51878f..c0b9ce0 100644
--- a/07_kubernetes_aks/main.tf
+++ b/07_kubernetes_aks/main.tf
@@ -16,7 +16,7 @@ resource "azurerm_kubernetes_cluster" "aks" {
node_count = var.system_node_count
vm_size = "Standard_DS2_v2"
type = "VirtualMachineScaleSets"
- availability_zones = [1, 2, 3]
+ # availability_zones = [1, 2, 3]
enable_auto_scaling = false
}
@@ -25,7 +25,7 @@ resource "azurerm_kubernetes_cluster" "aks" {
}
network_profile {
- load_balancer_sku = "Standard"
+ load_balancer_sku = "standard"
network_plugin = "kubenet" # azure (CNI)
}
}
\ No newline at end of file
diff --git a/07_kubernetes_aks/providers.tf b/07_kubernetes_aks/providers.tf
index 2c7c936..1ddcd8e 100644
--- a/07_kubernetes_aks/providers.tf
+++ b/07_kubernetes_aks/providers.tf
@@ -6,7 +6,7 @@ terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
- version = "2.78.0"
+ version = "3.54.0"
}
}
}
\ No newline at end of file
diff --git a/07_kubernetes_aks/terraform.tfvars b/07_kubernetes_aks/terraform.tfvars
index 3f9fbf9..a10a1ca 100644
--- a/07_kubernetes_aks/terraform.tfvars
+++ b/07_kubernetes_aks/terraform.tfvars
@@ -1,6 +1,6 @@
resource_group_name = "aks_terraform_rg"
location = "West Europe"
cluster_name = "terraform-aks"
-kubernetes_version = "1.19.3"
+kubernetes_version = "1.26.3"
system_node_count = 3
node_resource_group = "aks_terraform_resources_rg"
\ No newline at end of file
diff --git a/120_azapi_provider/Readme.md b/120_azapi_provider/Readme.md
new file mode 100644
index 0000000..d7a48d7
--- /dev/null
+++ b/120_azapi_provider/Readme.md
@@ -0,0 +1,38 @@
+# Using Azure Grafana and Prometheus workspace in AKS using Terraform
+
+## Introduction
+
+This lab shows how to use Terraform to provision an AKS cluster, Grafana and Monitor Workspace for Prometheus. All configured together to collect metrics from the cluster and expose it through Grafana dashboard.
+
+
+
+## Challenges
+
+Azure Monitor Workspace for Prometheus is a new service (in preview).
+It is not yet supported with ARM template or with Terraform resource.
+
+So, we'll use `azapi` terraform provider to create the Monitor Workspace for Prometheus.
+
+And we'll use a `local-exec` to run a command line to configure AKS with Prometheus.
+
+AKS, Grafana and Log Analytics are suported with ARM templates and Terraform.
+
+## Deploying the resources using Terraform
+
+To deploy the Terraform configuration files, run the following commands:
+
+```shell
+terraform init
+
+terraform plan -out tfplan
+
+terraform apply tfplan
+```
+
+## Cleanup resources
+
+To delete the creates resources, run the following command:
+
+```shell
+terraform destroy
+```
\ No newline at end of file
diff --git a/120_azapi_provider/aks.tf b/120_azapi_provider/aks.tf
new file mode 100644
index 0000000..b2dfb91
--- /dev/null
+++ b/120_azapi_provider/aks.tf
@@ -0,0 +1,29 @@
+# aks cluster
+resource "azurerm_kubernetes_cluster" "aks" {
+ name = "aks-cluster"
+ location = "westeurope"
+ resource_group_name = "rg-aks-cluster"
+ dns_prefix = "aks"
+ kubernetes_version = "1.25.5"
+
+ default_node_pool {
+ name = "default"
+ node_count = "3"
+ vm_size = "Standard_DS2_v2"
+ }
+
+ identity {
+ type = "SystemAssigned"
+ }
+
+ oms_agent {
+ log_analytics_workspace_id = azurerm_log_analytics_workspace.workspace.id
+ msi_auth_for_monitoring_enabled = true
+ }
+
+ lifecycle {
+ ignore_changes = [
+ monitor_metrics
+ ]
+ }
+}
diff --git a/120_azapi_provider/commands.sh b/120_azapi_provider/commands.sh
new file mode 100644
index 0000000..e331d02
--- /dev/null
+++ b/120_azapi_provider/commands.sh
@@ -0,0 +1,7 @@
+terraform init
+
+terraform plan -out tfplan
+
+terraform apply tfplan
+
+terraform destroy
\ No newline at end of file
diff --git a/120_azapi_provider/enable_prometheus.tf b/120_azapi_provider/enable_prometheus.tf
new file mode 100644
index 0000000..d3c94a0
--- /dev/null
+++ b/120_azapi_provider/enable_prometheus.tf
@@ -0,0 +1,22 @@
+resource "null_resource" "enable_azuremonitormetrics" {
+ # for windows
+ provisioner "local-exec" {
+ interpreter = ["PowerShell", "-Command"]
+ command = <<-EOT
+
+ az aks update --enable-azuremonitormetrics `
+ -g ${azurerm_kubernetes_cluster.aks.resource_group_name} `
+ -n ${azurerm_kubernetes_cluster.aks.name} `
+ --azure-monitor-workspace-resource-id ${azapi_resource.prometheus.id}
+ EOT
+ }
+
+ triggers = {
+ "key" = "value1"
+ }
+
+ # for linux
+ # provisioner "local-exec" {
+ # command = "az aks update --enable-azuremonitormetrics -g ${azurerm_kubernetes_cluster.aks.resource_group_name} -n ${azurerm_kubernetes_cluster.aks.name} --azure-monitor-workspace-resource-id ${azapi_resource.prometheus.id}"
+ # }
+}
diff --git a/120_azapi_provider/grafana.tf b/120_azapi_provider/grafana.tf
new file mode 100644
index 0000000..66d3864
--- /dev/null
+++ b/120_azapi_provider/grafana.tf
@@ -0,0 +1,47 @@
+resource "azurerm_dashboard_grafana" "grafana" {
+ name = var.grafana_name
+ resource_group_name = azurerm_resource_group.rg_monitoring.name
+ location = azurerm_resource_group.rg_monitoring.location
+ api_key_enabled = true
+ deterministic_outbound_ip_enabled = true
+ public_network_access_enabled = true
+ sku = "Standard"
+ zone_redundancy_enabled = true
+
+ azure_monitor_workspace_integrations {
+ resource_id = azapi_resource.prometheus.id
+ }
+
+ identity {
+ type = "SystemAssigned" # The only possible values is SystemAssigned
+ }
+}
+
+data "azurerm_client_config" "current" {}
+
+# assign current user as Grafana Admin
+resource "azurerm_role_assignment" "role_grafana_admin" {
+ scope = azurerm_dashboard_grafana.grafana.id
+ role_definition_name = "Grafana Admin"
+ principal_id = data.azurerm_client_config.current.object_id
+}
+
+resource "azurerm_role_assignment" "role_monitoring_data_reader" {
+ scope = azapi_resource.prometheus.id
+ role_definition_name = "Monitoring Data Reader"
+ principal_id = azurerm_dashboard_grafana.grafana.identity.0.principal_id
+}
+
+data "azurerm_subscription" "current" {}
+
+# https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/grafana-plugin
+# (Optional) Grafana to monitor all Azure resources
+resource "azurerm_role_assignment" "role_monitoring_reader" {
+ scope = data.azurerm_subscription.current.id
+ role_definition_name = "Monitoring Reader"
+ principal_id = azurerm_dashboard_grafana.grafana.identity.0.principal_id
+}
+
+output "garafana_endpoint" {
+ value = azurerm_dashboard_grafana.grafana.endpoint
+}
\ No newline at end of file
diff --git a/120_azapi_provider/images/architecture.png b/120_azapi_provider/images/architecture.png
new file mode 100644
index 0000000..4b45bc4
Binary files /dev/null and b/120_azapi_provider/images/architecture.png differ
diff --git a/120_azapi_provider/log_analytics.tf b/120_azapi_provider/log_analytics.tf
new file mode 100644
index 0000000..aa536e1
--- /dev/null
+++ b/120_azapi_provider/log_analytics.tf
@@ -0,0 +1,20 @@
+resource "azurerm_log_analytics_workspace" "workspace" {
+ name = "log-analytics-workspace"
+ resource_group_name = azurerm_resource_group.rg_monitoring.name
+ location = var.resources_location
+ sku = "PerGB2018" # PerGB2018, Free, PerNode, Premium, Standard, Standalone, Unlimited, CapacityReservation
+ retention_in_days = 30 # possible values are either 7 (Free Tier only) or range between 30 and 730
+}
+
+resource "azurerm_log_analytics_solution" "solution" {
+ solution_name = "ContainerInsights"
+ location = azurerm_log_analytics_workspace.workspace.location
+ resource_group_name = azurerm_log_analytics_workspace.workspace.resource_group_name
+ workspace_resource_id = azurerm_log_analytics_workspace.workspace.id
+ workspace_name = azurerm_log_analytics_workspace.workspace.name
+
+ plan {
+ publisher = "Microsoft"
+ product = "OMSGallery/ContainerInsights"
+ }
+}
\ No newline at end of file
diff --git a/120_azapi_provider/prometheus.tf b/120_azapi_provider/prometheus.tf
new file mode 100644
index 0000000..c5a21d5
--- /dev/null
+++ b/120_azapi_provider/prometheus.tf
@@ -0,0 +1,7 @@
+# https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/azure-monitor-workspace-overview?tabs=resource-manager#create-an-azure-monitor-workspace
+resource "azapi_resource" "prometheus" {
+ type = "microsoft.monitor/accounts@2021-06-03-preview"
+ name = "monitor-workspace-aks"
+ parent_id = azurerm_resource_group.rg_monitoring.id
+ location = azurerm_resource_group.rg_monitoring.location
+}
\ No newline at end of file
diff --git a/120_azapi_provider/provider.tf b/120_azapi_provider/provider.tf
new file mode 100644
index 0000000..4563c2e
--- /dev/null
+++ b/120_azapi_provider/provider.tf
@@ -0,0 +1,34 @@
+terraform {
+
+ required_version = ">= 1.2.8"
+
+ required_providers {
+
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "= 3.50.0"
+ }
+
+ azuread = {
+ source = "hashicorp/azuread"
+ version = "= 2.36.0"
+ }
+
+ azapi = {
+ source = "Azure/azapi"
+ version = "1.4.0"
+ }
+ }
+}
+
+provider "azurerm" {
+ features {}
+}
+
+# Configure the Azure Active Directory Provider
+provider "azuread" { # default takes current user/identity tenant
+}
+
+provider "azapi" {
+ # Configuration options
+}
diff --git a/120_azapi_provider/resource_group.tf b/120_azapi_provider/resource_group.tf
new file mode 100644
index 0000000..276e5b7
--- /dev/null
+++ b/120_azapi_provider/resource_group.tf
@@ -0,0 +1,9 @@
+resource "azurerm_resource_group" "rg_aks_cluster" {
+ name = var.rg_aks_cluster
+ location = var.resources_location
+}
+
+resource "azurerm_resource_group" "rg_monitoring" {
+ name = var.rg_monitoring
+ location = var.resources_location
+}
\ No newline at end of file
diff --git a/120_azapi_provider/variables.tf b/120_azapi_provider/variables.tf
new file mode 100644
index 0000000..e07d3e6
--- /dev/null
+++ b/120_azapi_provider/variables.tf
@@ -0,0 +1,29 @@
+variable "resources_location" {
+ type = string
+ default = "westeurope"
+}
+
+variable "rg_aks_cluster" {
+ type = string
+ default = "rg-aks-cluster"
+}
+
+variable "rg_monitoring" {
+ type = string
+ default = "rg-monitoring"
+}
+
+variable "aks_name" {
+ type = string
+ default = "aks-cluster"
+}
+
+variable "grafana_name" {
+ type = string
+ default = "azure-grafana-13579"
+}
+
+variable "prometheus_name" {
+ type = string
+ default = "azure-prometheus"
+}
diff --git a/121_appservice_domain/Readme.md b/121_appservice_domain/Readme.md
new file mode 100644
index 0000000..13a9d4b
--- /dev/null
+++ b/121_appservice_domain/Readme.md
@@ -0,0 +1,127 @@
+# Azure App Service Domain in Terraform
+
+## News
+
+This is now available as Terraform module on Terraform Registry: https://registry.terraform.io/modules/HoussemDellai/appservice-domain/azapi/
+
+## Problem
+
+You can create a custom domain name in Azure using App Service Domain service.
+You can do that using Azure portal or Azure CLI.
+But you cannot do that using Terraform for Azure provider.
+Because that is not implemented yet.
+Creating a custom domain in infra as code tool like Terraform might not be that much appealing for enterprises.
+They would purchase their domain name manually, just once. Infra as code doesn't make lots of sense here.
+
+However for labs, workshops and demonstrations, this is very useful to make the lab more realistic.
+
+## Solution
+
+We'll provide a Terraform implementation for creating a custom domain name using Azure App Service Domain.
+We'll use `AzApi` provider to create the resource. More info about AzApi here: https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/azapi_resource.
+
+The AzApi will call the REST API and pass the required JSON file containing the needed attributes.
+Take a look at the REST API for App Service Domain here: https://learn.microsoft.com/en-us/rest/api/appservice/domains/create-or-update
+
+We also create an Azure DNS Zone to manage and configure the domain name.
+
+And we create an A record "test" to make sure the configuration works.
+
+The complete Terraform implementation is in this current folder.
+But here is how to use it.
+
+```terraform
+resource "azurerm_dns_zone" "dns_zone" {
+ name = var.domain_name
+ resource_group_name = azurerm_resource_group.rg.name
+}
+
+resource "azapi_resource" "appservice_domain" {
+ type = "Microsoft.DomainRegistration/domains@2022-09-01"
+ name = var.domain_name
+ parent_id = azurerm_resource_group.rg.id
+ location = "global"
+ schema_validation_enabled = true
+
+ body = jsonencode({
+
+ properties = {
+ autoRenew = false
+ dnsType = "AzureDns"
+ dnsZoneId = azurerm_dns_zone.dns_zone.id
+ privacy = false
+
+ consent = {
+ agreementKeys = ["DNRA"]
+ agreedBy = var.AgreedBy_IP_v6 # "2a04:cec0:11d9:24c8:8898:3820:8631:d83"
+ agreedAt = var.AgreedAt_DateTime # "2023-08-10T11:50:59.264Z"
+ }
+
+```
+
+## Deploy the resources using Terraform
+
+Choose the custom domain name you want to purchase in the file `terraform.tfvars`.
+
+Then run the following Terraform commands from within the current folder.
+
+```powershell
+terraform init
+terraform plan -out tfplan
+terraform apply tfplan
+```
+
+## Test the deployment
+
+Verify you have two resources created within the resource group.
+
+
+
+Verify that custom domain name works.
+You should see the IP address we used in A record which is `1.2.3.4`.
+
+```powershell
+nslookup test. # replace with domain name
+# Server: bbox.lan
+# Address: 2001:861:5e62:69c0:861e:a3ff:fea2:796c
+# Non-authoritative answer:
+# Name: test.houssem13.com
+# Address: 1.2.3.4
+```
+
+## Creating a custom domain name using Azure CLI
+
+In this lab we used Terraform to create the domain name.
+But still you can just use Azure portal or command line.
+
+
+
+Make sure you fill the `contact_info.json` file. It is required to create domain name.
+
+```powershell
+az group create -n rg-dns-domain -l westeurope -o table
+
+az appservice domain create `
+ --resource-group rg-dns-domain `
+ --hostname "houssem.com" `
+ --contact-info=@'contact_info.json' `
+ --accept-terms
+```
+
+## Video tutorial
+
+Here is a Youtube video explaining how this works: [https://www.youtube.com/watch?v=ptdAcsG2ROI](https://www.youtube.com/watch?v=ptdAcsG2ROI)
+
+![](https://github.com/HoussemDellai/terraform-azapi-appservice-domain/blob/main/images/youtube.png?raw=true)
+
+## Important notes
+
+You should use a Pay-As-You-Go azure subscription to be able to create Azure App Service Domain.
+MSDN/VisualStudio and Free Azure subscriptions doesn't work.
+
+Within the terraform config file, you can change the contact info for the contactAdmin, contactRegistrant, contactBilling and contactTech.
+It worked for me when reusing the same contact !
+
+## What is next ?
+
+You can explore App Service Domain with Azure Container Apps (ACA) in this lab: https://github.com/HoussemDellai/aca-course/tree/main/14_aca_custom_domain.
\ No newline at end of file
diff --git a/121_appservice_domain/appservice_domain.tf b/121_appservice_domain/appservice_domain.tf
new file mode 100644
index 0000000..511538d
--- /dev/null
+++ b/121_appservice_domain/appservice_domain.tf
@@ -0,0 +1,85 @@
+# App Service Domain
+# REST API reference: https://docs.microsoft.com/en-us/rest/api/appservice/domains/createorupdate
+resource "azapi_resource" "appservice_domain" {
+ type = "Microsoft.DomainRegistration/domains@2022-09-01"
+ name = var.domain_name
+ parent_id = azurerm_resource_group.rg.id
+ location = "global"
+ schema_validation_enabled = true
+
+ body = jsonencode({
+
+ properties = {
+ autoRenew = false
+ dnsType = "AzureDns"
+ dnsZoneId = azurerm_dns_zone.dns_zone.id
+ privacy = false
+
+ consent = {
+ agreementKeys = ["DNRA"]
+ agreedBy = var.AgreedBy_IP_v6 # "2a04:cec0:11d9:24c8:8898:3820:8631:d83"
+ agreedAt = var.AgreedAt_DateTime # "2023-08-10T11:50:59.264Z"
+ }
+
+ contactAdmin = {
+ nameFirst = var.contact.nameFirst
+ nameLast = var.contact.nameLast
+ email = var.contact.email
+ phone = var.contact.phone
+
+ addressMailing = {
+ address1 = var.contact.addressMailing.address1
+ city = var.contact.addressMailing.city
+ state = var.contact.addressMailing.state
+ country = var.contact.addressMailing.country
+ postalCode = var.contact.addressMailing.postalCode
+ }
+ }
+
+ contactRegistrant = {
+ nameFirst = var.contact.nameFirst
+ nameLast = var.contact.nameLast
+ email = var.contact.email
+ phone = var.contact.phone
+
+ addressMailing = {
+ address1 = var.contact.addressMailing.address1
+ city = var.contact.addressMailing.city
+ state = var.contact.addressMailing.state
+ country = var.contact.addressMailing.country
+ postalCode = var.contact.addressMailing.postalCode
+ }
+ }
+
+ contactBilling = {
+ nameFirst = var.contact.nameFirst
+ nameLast = var.contact.nameLast
+ email = var.contact.email
+ phone = var.contact.phone
+
+ addressMailing = {
+ address1 = var.contact.addressMailing.address1
+ city = var.contact.addressMailing.city
+ state = var.contact.addressMailing.state
+ country = var.contact.addressMailing.country
+ postalCode = var.contact.addressMailing.postalCode
+ }
+ }
+
+ contactTech = {
+ nameFirst = var.contact.nameFirst
+ nameLast = var.contact.nameLast
+ email = var.contact.email
+ phone = var.contact.phone
+
+ addressMailing = {
+ address1 = var.contact.addressMailing.address1
+ city = var.contact.addressMailing.city
+ state = var.contact.addressMailing.state
+ country = var.contact.addressMailing.country
+ postalCode = var.contact.addressMailing.postalCode
+ }
+ }
+ }
+ })
+}
diff --git a/121_appservice_domain/contact_info.json b/121_appservice_domain/contact_info.json
new file mode 100644
index 0000000..c90b5a5
--- /dev/null
+++ b/121_appservice_domain/contact_info.json
@@ -0,0 +1,59 @@
+{
+ "address1": {
+ "value": "90 avenue des Champs Elysees",
+ "required": true
+ },
+ "address2": {
+ "value": "",
+ "required": false
+ },
+ "city": {
+ "value": "Paris",
+ "required": true
+ },
+ "country": {
+ "value": "FR",
+ "required": true,
+ "options": []
+ },
+ "postal_code": {
+ "value": "75008",
+ "required": true
+ },
+ "state": {
+ "value": "Ile de France",
+ "required": true
+ },
+ "email": {
+ "value": "houssem.dellai@email.com",
+ "required": true
+ },
+ "fax": {
+ "value": "",
+ "required": false
+ },
+ "job_title": {
+ "value": "",
+ "required": false
+ },
+ "name_first": {
+ "value": "Houssem",
+ "required": true
+ },
+ "name_last": {
+ "value": "Dellai",
+ "required": true
+ },
+ "name_middle": {
+ "value": "",
+ "required": false
+ },
+ "organization": {
+ "value": "",
+ "required": false
+ },
+ "phone": {
+ "value": "+33.762954328",
+ "required": true
+ }
+}
\ No newline at end of file
diff --git a/121_appservice_domain/dns_zone.tf b/121_appservice_domain/dns_zone.tf
new file mode 100644
index 0000000..b82d056
--- /dev/null
+++ b/121_appservice_domain/dns_zone.tf
@@ -0,0 +1,14 @@
+# DNS Zone to configure the domain name
+resource "azurerm_dns_zone" "dns_zone" {
+ name = var.domain_name
+ resource_group_name = azurerm_resource_group.rg.name
+}
+
+# DNS Zone A record
+resource "azurerm_dns_a_record" "dns_a_record" {
+ name = "test"
+ zone_name = azurerm_dns_zone.dns_zone.name
+ resource_group_name = azurerm_resource_group.rg.name
+ ttl = 300
+ records = ["1.2.3.4"] # just example IP address
+}
diff --git a/121_appservice_domain/images/portal.png b/121_appservice_domain/images/portal.png
new file mode 100644
index 0000000..0f7ef1c
Binary files /dev/null and b/121_appservice_domain/images/portal.png differ
diff --git a/121_appservice_domain/images/resources.png b/121_appservice_domain/images/resources.png
new file mode 100644
index 0000000..7263b02
Binary files /dev/null and b/121_appservice_domain/images/resources.png differ
diff --git a/121_appservice_domain/images/youtube.png b/121_appservice_domain/images/youtube.png
new file mode 100644
index 0000000..6da94bd
Binary files /dev/null and b/121_appservice_domain/images/youtube.png differ
diff --git a/121_appservice_domain/providers.tf b/121_appservice_domain/providers.tf
new file mode 100644
index 0000000..27a6e95
--- /dev/null
+++ b/121_appservice_domain/providers.tf
@@ -0,0 +1,23 @@
+terraform {
+
+ required_version = ">= 1.2.8"
+
+ required_providers {
+
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "= 3.85.0"
+ }
+
+ azapi = {
+ source = "Azure/azapi"
+ version = "1.11.0"
+ }
+ }
+}
+
+provider "azurerm" {
+ features {}
+}
+
+provider "azapi" {}
diff --git a/121_appservice_domain/rg.tf b/121_appservice_domain/rg.tf
new file mode 100644
index 0000000..073ef9e
--- /dev/null
+++ b/121_appservice_domain/rg.tf
@@ -0,0 +1,5 @@
+# Resource Group
+resource "azurerm_resource_group" "rg" {
+ name = var.rg_name
+ location = var.location
+}
\ No newline at end of file
diff --git a/121_appservice_domain/terraform.tfvars b/121_appservice_domain/terraform.tfvars
new file mode 100644
index 0000000..809fda3
--- /dev/null
+++ b/121_appservice_domain/terraform.tfvars
@@ -0,0 +1,19 @@
+domain_name = "houssem13.com"
+rg_name = "rg-dns-demo"
+location = "westeurope"
+AgreedBy_IP_v6 = "2a04:cec0:11d9:24c8:8898:3820:8631:d83"
+AgreedAt_DateTime = "2023-08-10T11:50:59.264Z"
+
+contact = {
+ nameFirst = "Houssem"
+ nameLast = "Dellai"
+ email = "houssem.dellai@live.com" # you'll get verification email
+ phone = "+33.762954328"
+ addressMailing = {
+ address1 = "1 Microsoft Way"
+ city = "Redmond"
+ state = "WA"
+ country = "US"
+ postalCode = "98052"
+ }
+}
diff --git a/121_appservice_domain/variables.tf b/121_appservice_domain/variables.tf
new file mode 100644
index 0000000..b197b45
--- /dev/null
+++ b/121_appservice_domain/variables.tf
@@ -0,0 +1,39 @@
+variable "domain_name" {
+ type = string
+ validation {
+ condition = length(var.domain_name) > 0 && (endswith(var.domain_name, ".com") || endswith(var.domain_name, ".net") || endswith(var.domain_name, ".co.uk") || endswith(var.domain_name, ".org") || endswith(var.domain_name, ".nl") || endswith(var.domain_name, ".in") || endswith(var.domain_name, ".biz") || endswith(var.domain_name, ".org.uk") || endswith(var.domain_name, ".co.in"))
+ error_message = "Available top level domains are: com, net, co.uk, org, nl, in, biz, org.uk, and co.in"
+ }
+}
+
+variable "rg_name" {
+ type = string
+}
+
+variable "location" {
+ type = string
+}
+
+variable "AgreedBy_IP_v6" { # "2a04:cec0:11d9:24c8:8898:3820:8631:d83"
+ type = string
+}
+
+variable "AgreedAt_DateTime" { # "2023-08-10T11:50:59.264Z"
+ type = string
+}
+
+variable "contact" {
+ type = object({
+ nameFirst = string
+ nameLast = string
+ email = string
+ phone = string
+ addressMailing = object({
+ address1 = string
+ city = string
+ state = string
+ country = string
+ postalCode = string
+ })
+ })
+}
diff --git a/130_module_dependencies/Readme.md b/130_module_dependencies/Readme.md
new file mode 100644
index 0000000..2046e9e
--- /dev/null
+++ b/130_module_dependencies/Readme.md
@@ -0,0 +1,202 @@
+# Learn by doing: Terraform modules dependencies
+
+## Introduction
+
+In this lab, you will deep dive into learning Terraform modules dependencies.
+Through examples, you will learn how Terraform manages the chaining of resource creation regarding dependencies.
+This will allow you to better understand Terraform behaviour and optimize resource creation time.
+
+You will work with two sample modules provided under the `\modules` folder: keyvault and storage_account.
+Keyvaul module creates an Azure Key vault and a Public IP resources.
+Storage_account creates an Azure Storage Account and a Public IP resources.
+The Public IP resource has nothing to do with the storage account or key vault.
+It is needed just for demoing purposes.
+
+Both modules depends implicitly on the resource group created at the root resource as they reference the resource group name and location.
+
+## Scenario 1: no dependencies between modules
+
+In this scenarion, module storage account does not depend on module keyvault.
+There are no explicit or implicit dependency.
+
+In the following example, the module doesn't depend on any other module.
+It just depends on a resource group at the root configuration.
+
+```hcl
+resource "azurerm_resource_group" "rg" {
+ name = "rg-prod"
+ location = "westeurope"
+}
+
+module "keyvault" {
+ source = "./modules/keyvault"
+
+ key_vault_name = "kv123579"
+ resource_group_name = azurerm_resource_group.rg.name
+}
+
+module "storage_account" {
+ source = "./modules/storage_account"
+
+ storage_account_name = "strg1235790"
+ resource_group_name = azurerm_resource_group.rg.name
+}
+```
+
+Let's run two independant modules and see what will happen.
+Make sure that only resource group, key vault and storage account are uncommented.
+
+```terraform
+terraform init
+terraform apply -auto-approve
+
+# azurerm_resource_group.rg: Creating...
+# azurerm_resource_group.rg: Creation complete after 2s
+# module.storage_account.azurerm_public_ip.pip: Creating...
+# module.keyvault.azurerm_key_vault.keyvault: Creating...
+# module.keyvault.azurerm_public_ip.pip: Creating...
+# module.storage_account.azurerm_storage_account.storage: Creating...
+# module.keyvault.azurerm_public_ip.pip: Creation complete after 2s
+# module.storage_account.azurerm_public_ip.pip: Creation complete after 2s
+# module.storage_account.azurerm_storage_account.storage: Creation complete after 22s
+# module.keyvault.azurerm_key_vault.keyvault: Creation complete after 2m9s
+```
+
+Note from the output, because there is no dependencies between modules, the resources will be created in parallel.
+
+![](images/scenario-1.png)
+
+> Resources from independant modules will be created in parallel, with no specified order.
+
+Cleanup the resources before continuing with the next scenario.
+
+```terraform
+terraform destroy -auto-approve
+```
+
+## Scenario 2: module depends explicitly on another module
+
+In this scenario, you explore a Terraform keyword called `depends_on`.
+This was introduced first to set dependencies between resources.
+Starting from version 0.13 of terraform, `depends_on` could be used also for setting dependencies between modules.
+
+Let's see how that works.
+You will have two modules where the second module depends on the first one.
+
+The syntax in Terraform is the following.
+
+```hcl
+module "storage_account" {
+ source = "./modules/storage_account"
+ storage_account_name = "strg1235790"
+ resource_group_name = azurerm_resource_group.rg.name
+ depends_on = [ module.keyvault ] # explicit dependency on entire module
+}
+```
+
+To test this scanario, make sure that only resource group, key vault and storage account (scenatrio 2) are uncommented.
+Then run the terraform apply command to create the resources.
+
+```terraform
+terraform apply -auto-approve
+
+# azurerm_resource_group.rg: Creating...
+# azurerm_resource_group.rg: Creation complete after 1s
+# module.keyvault.azurerm_public_ip.pip: Creating...
+# module.keyvault.azurerm_key_vault.keyvault: Creating...
+# module.keyvault.azurerm_public_ip.pip: Creation complete after 2s
+# module.keyvault.azurerm_key_vault.keyvault: Creation complete after 2m40s
+# module.storage_account.azurerm_public_ip.pip: Creating...
+# module.storage_account.azurerm_storage_account.storage: Creating...
+# module.storage_account.azurerm_public_ip.pip: Creation complete after 3s
+# module.storage_account.azurerm_storage_account.storage: Creation complete after 27s
+```
+
+Note how the resources from the storage account module are created after all the resources from the kayvault module.
+
+![](images/scenario-2.png)
+
+>The impact of explicit dependency between modules is that the resources from the dependant module will be delayed until the creation of all resources from original module.
+
+## Scenario 3: module depends implicitly only on a specific resource from another module
+
+In this scenario, you will explore an implicit dependency on a specific resource within a module.
+An impilict dependency is a direct reference to a resource or data property like the name, location, tags, etc.
+Let's run an experiment to see the impact.
+
+```hcl
+module "storage_account" {
+ source = "./modules/storage_account"
+ storage_account_name = module.keyvault.key_vault_name # implicit dependency on resource from another module
+ resource_group_name = azurerm_resource_group.rg.name
+}
+```
+
+Run Terraform command to create the resources.
+
+```terraform
+terraform apply -auto-approve
+
+# azurerm_resource_group.rg: Creating...
+# azurerm_resource_group.rg: Creation complete after 0s
+# module.keyvault.azurerm_public_ip.pip: Creating...
+# module.storage_account.azurerm_public_ip.pip: Creating...
+# module.keyvault.azurerm_key_vault.keyvault: Creating...
+# module.keyvault.azurerm_public_ip.pip: Creation complete after 3s
+# module.storage_account.azurerm_public_ip.pip: Creation complete after 3s
+# module.keyvault.azurerm_key_vault.keyvault: Creation complete after 2m12s
+# module.storage_account.azurerm_storage_account.storage: Creating...
+# module.storage_account.azurerm_storage_account.storage: Creation complete after 25s
+```
+
+Note frm the results of this experiment that creation of the dependant resource, which is the the storage account, started only after the creation of the key vault, as it depends implicitly on it.
+Resources in both modules still could be created in parallel.
+In scenario 1, terraform started by creating 2 resources in parallel.
+However, in this scenario, terraform started by creating 3 resources in parallel.
+This results in reducing the execution time.
+
+![](images/scenario-3.png)
+
+>Dependency on a specific resource from a module results in less execution time than dependency on the entire module.
+
+## Conclusion
+
+You learned in this lab the different options and impact for setting up dependencies between modules and resources.
+The learning is that you should prefer to use implicit dependency on a specific resource rather than a dependency on an entire module.
+
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.3.4 |
+| [azurerm](#requirement\_azurerm) | ~> 3.70.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [azurerm](#provider\_azurerm) | 3.70.0 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [keyvault](#module\_keyvault) | ./modules/keyvault | n/a |
+| [storage\_account](#module\_storage\_account) | ./modules/storage_account | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [azurerm_resource_group.rg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
+
+## Inputs
+
+No inputs.
+
+## Outputs
+
+No outputs.
+
\ No newline at end of file
diff --git a/130_module_dependencies/images/scenario-1.png b/130_module_dependencies/images/scenario-1.png
new file mode 100644
index 0000000..1d6cae9
Binary files /dev/null and b/130_module_dependencies/images/scenario-1.png differ
diff --git a/130_module_dependencies/images/scenario-2.png b/130_module_dependencies/images/scenario-2.png
new file mode 100644
index 0000000..8796905
Binary files /dev/null and b/130_module_dependencies/images/scenario-2.png differ
diff --git a/130_module_dependencies/images/scenario-3.png b/130_module_dependencies/images/scenario-3.png
new file mode 100644
index 0000000..18c1c21
Binary files /dev/null and b/130_module_dependencies/images/scenario-3.png differ
diff --git a/130_module_dependencies/main.tf b/130_module_dependencies/main.tf
new file mode 100644
index 0000000..e852c00
--- /dev/null
+++ b/130_module_dependencies/main.tf
@@ -0,0 +1,41 @@
+resource "azurerm_resource_group" "rg" {
+ name = "rg-prod"
+ location = "westeurope"
+}
+
+module "keyvault" {
+ source = "./modules/keyvault"
+
+ key_vault_name = "kv123579"
+ resource_group_name = azurerm_resource_group.rg.name
+}
+
+# Scenario 1: module storage_account does not depend on module keyvault
+
+# module "storage_account" {
+# source = "./modules/storage_account"
+
+# storage_account_name = "strg1235790"
+# resource_group_name = azurerm_resource_group.rg.name
+# }
+
+# Scenario 2: module storage_account depends explicitly on module keyvault
+
+# module "storage_account" {
+# source = "./modules/storage_account"
+
+# storage_account_name = "strg1235790"
+# resource_group_name = azurerm_resource_group.rg.name
+
+# depends_on = [ module.keyvault ] # explicit dependency on entire module
+# }
+
+# Scenario 3: module storage_account depends implicitly on module keyvault
+# Depends only on the public IP from module keyvault
+
+module "storage_account" {
+ source = "./modules/storage_account"
+
+ storage_account_name = module.keyvault.key_vault_name # implicit dependency on key_vault_name output
+ resource_group_name = azurerm_resource_group.rg.name
+}
\ No newline at end of file
diff --git a/130_module_dependencies/modules/keyvault/Readme.md b/130_module_dependencies/modules/keyvault/Readme.md
new file mode 100644
index 0000000..e3baec5
--- /dev/null
+++ b/130_module_dependencies/modules/keyvault/Readme.md
@@ -0,0 +1,38 @@
+
+## Requirements
+
+No requirements.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [azurerm](#provider\_azurerm) | n/a |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [azurerm_key_vault.keyvault](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault) | resource |
+| [azurerm_public_ip.pip](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource |
+| [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [key\_vault\_name](#input\_key\_vault\_name) | Name of the key vault | `string` | n/a | yes |
+| [location](#input\_location) | Location of the resources | `string` | n/a | yes |
+| [resource\_group\_name](#input\_resource\_group\_name) | Name of the resource group | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [key\_vault\_id](#output\_key\_vault\_id) | n/a |
+| [key\_vault\_name](#output\_key\_vault\_name) | n/a |
+
\ No newline at end of file
diff --git a/130_module_dependencies/modules/keyvault/main.tf b/130_module_dependencies/modules/keyvault/main.tf
new file mode 100644
index 0000000..85df3f5
--- /dev/null
+++ b/130_module_dependencies/modules/keyvault/main.tf
@@ -0,0 +1,17 @@
+data "azurerm_client_config" "current" {}
+
+resource "azurerm_key_vault" "keyvault" {
+ name = var.key_vault_name
+ resource_group_name = var.resource_group_name
+ location = "westeurope"
+ sku_name = "standard"
+ tenant_id = data.azurerm_client_config.current.tenant_id
+}
+
+resource "azurerm_public_ip" "pip" {
+ name = "pip-keyvault"
+ location = "westeurope"
+ resource_group_name = var.resource_group_name
+ allocation_method = "Dynamic"
+ sku = "Basic"
+}
diff --git a/130_module_dependencies/modules/keyvault/output.tf b/130_module_dependencies/modules/keyvault/output.tf
new file mode 100644
index 0000000..085b700
--- /dev/null
+++ b/130_module_dependencies/modules/keyvault/output.tf
@@ -0,0 +1,7 @@
+output "key_vault_name" {
+ value = azurerm_key_vault.keyvault.name
+}
+
+output "key_vault_id" {
+ value = azurerm_key_vault.keyvault.id
+}
diff --git a/130_module_dependencies/modules/keyvault/provider.tf b/130_module_dependencies/modules/keyvault/provider.tf
new file mode 100644
index 0000000..e69de29
diff --git a/130_module_dependencies/modules/keyvault/variables.tf b/130_module_dependencies/modules/keyvault/variables.tf
new file mode 100644
index 0000000..0c13aa3
--- /dev/null
+++ b/130_module_dependencies/modules/keyvault/variables.tf
@@ -0,0 +1,9 @@
+variable resource_group_name {
+ description = "Name of the resource group"
+ type = string
+}
+
+variable "key_vault_name" {
+ description = "Name of the key vault"
+ type = string
+}
\ No newline at end of file
diff --git a/130_module_dependencies/modules/storage_account/Readme.md b/130_module_dependencies/modules/storage_account/Readme.md
new file mode 100644
index 0000000..7c63ee4
--- /dev/null
+++ b/130_module_dependencies/modules/storage_account/Readme.md
@@ -0,0 +1,37 @@
+
+## Requirements
+
+No requirements.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [azurerm](#provider\_azurerm) | n/a |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [azurerm_public_ip.pip](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource |
+| [azurerm_storage_account.storage](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [location](#input\_location) | Location of the resources | `string` | n/a | yes |
+| [resource\_group\_name](#input\_resource\_group\_name) | Name of the resource group | `string` | n/a | yes |
+| [storage\_account\_name](#input\_storage\_account\_name) | Name of the storage account | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [storage\_account\_id](#output\_storage\_account\_id) | n/a |
+| [storage\_account\_name](#output\_storage\_account\_name) | n/a |
+
\ No newline at end of file
diff --git a/130_module_dependencies/modules/storage_account/main.tf b/130_module_dependencies/modules/storage_account/main.tf
new file mode 100644
index 0000000..4fe2431
--- /dev/null
+++ b/130_module_dependencies/modules/storage_account/main.tf
@@ -0,0 +1,15 @@
+resource "azurerm_storage_account" "storage" {
+ name = var.storage_account_name
+ resource_group_name = var.resource_group_name
+ location = "westeurope"
+ account_tier = "Standard"
+ account_replication_type = "LRS"
+}
+
+resource azurerm_public_ip "pip" {
+ name = "pip-storage"
+ resource_group_name = var.resource_group_name
+ location = "westeurope"
+ allocation_method = "Dynamic"
+ sku = "Basic"
+}
\ No newline at end of file
diff --git a/130_module_dependencies/modules/storage_account/output.tf b/130_module_dependencies/modules/storage_account/output.tf
new file mode 100644
index 0000000..133b58a
--- /dev/null
+++ b/130_module_dependencies/modules/storage_account/output.tf
@@ -0,0 +1,7 @@
+output "storage_account_name" {
+ value = azurerm_storage_account.storage.name
+}
+
+output "storage_account_id" {
+ value = azurerm_storage_account.storage.id
+}
\ No newline at end of file
diff --git a/130_module_dependencies/modules/storage_account/provider.tf b/130_module_dependencies/modules/storage_account/provider.tf
new file mode 100644
index 0000000..e69de29
diff --git a/130_module_dependencies/modules/storage_account/variables.tf b/130_module_dependencies/modules/storage_account/variables.tf
new file mode 100644
index 0000000..2ee8b73
--- /dev/null
+++ b/130_module_dependencies/modules/storage_account/variables.tf
@@ -0,0 +1,9 @@
+variable "resource_group_name" {
+ description = "Name of the resource group"
+ type = string
+}
+
+variable "storage_account_name" {
+ description = "Name of the storage account"
+ type = string
+}
\ No newline at end of file
diff --git a/130_module_dependencies/provider.tf b/130_module_dependencies/provider.tf
new file mode 100644
index 0000000..995f0d5
--- /dev/null
+++ b/130_module_dependencies/provider.tf
@@ -0,0 +1,18 @@
+# Configure the Azure provider
+terraform {
+ required_providers {
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "~> 3.70.0"
+ }
+ }
+ required_version = ">= 1.3.4"
+}
+
+provider "azurerm" {
+ features {
+ resource_group {
+ prevent_deletion_if_contains_resources = false
+ }
+ }
+}
diff --git a/130_module_dependencies/scenario-1.png b/130_module_dependencies/scenario-1.png
new file mode 100644
index 0000000..1d6cae9
Binary files /dev/null and b/130_module_dependencies/scenario-1.png differ
diff --git a/130_module_dependencies/scenario-2.png b/130_module_dependencies/scenario-2.png
new file mode 100644
index 0000000..8796905
Binary files /dev/null and b/130_module_dependencies/scenario-2.png differ
diff --git a/140_logic_app/README.md b/140_logic_app/README.md
new file mode 100644
index 0000000..3c3c78c
--- /dev/null
+++ b/140_logic_app/README.md
@@ -0,0 +1,203 @@
+# Deploying Logic Apps using Terraform
+
+## Introduction
+
+In this lab, you will learn how to create and deploy an Azure Logic App using Terraform.
+
+Logic Apps are invented to make it easy for developers and also non-developers to create applications called workflows.
+It uses a visual designer where you can drag and drop actions instead of writing code.
+
+Behind the scenes, a JSON ARM template is generated to save the workflow configuration.
+
+![](images/action-send-email.png)
+
+You can export that ARM template and use to redeploy the same Logic App workflow.
+
+This approach is much more easier and faster than creating the Logic App using ARM templates without using the visual designer.
+That is because the syntax of the Logic Apps takes a bit long time to understand it.
+
+## Issue with Terraform provider's support for Logic Apps
+
+The visual designer generates ARM template. What about Terraform ?
+
+Terraform defines resources to create a workflow, triggers and actions.
+But for the actions it relies on JSON ARM template. And it is a 'headache' to configure.
+You will find it much more practical to just use the exported ARM template and deploy it as it is using `azurerm_resource_group_template_deployment`.
+
+## Creating Logic App using Azure portal
+
+In order to understand how Logic App works, you will need to create the Logic App workflow manually using the visual designer.
+You want to to use Logic App to send an email when you trigger it.
+
+Start be creating a new Logic App resource in Azure portal. Then go to `Logic app designer` section to start the creation.
+
+![](images/choose-action.png)
+
+The workflow will be composed of two components:
+- A trigger of type HTTP trigger that accepts a JSON payload containing the email destination and content
+- An Action to compose and send email using `outlook` connector, to the destination email from the trigger
+
+The `outlook` action will use your own outlook user identity to send emails on your behalf.
+
+The end result should be like this.
+
+![](images/view-code.png)
+
+## Deploying Logic App using Terraform and ARM template
+
+Now that the Logic App ARM template is generated, you can go to export it.
+
+![](images/view-code-workflow.png)
+
+You will use that ARM template to deploy it using Terraform with `azurerm_resource_group_template_deployment`.
+
+```hcl
+
+resource "azurerm_resource_group_template_deployment" "logic_app" {
+ name = "logic-app-deploy"
+ resource_group_name = azurerm_resource_group.rg.name
+ deployment_mode = "Incremental" # "Complete" #
+
+ parameters_content = jsonencode({
+ workflows_logic_app_name = { value = "logic-app-demo-135791555" }
+ connections_outlook_externalid = { value = azurerm_api_connection.api_connection_outlook.id }
+ connections_outlook_id = { value = data.azurerm_managed_api.managed_api_outlook.id }
+ })
+
+ template_content = <Hello,
We are happy to tell you your video file is now available to download on this link: @{triggerBody()?['sas_url']}
Thank you,
",
+ "Importance": "Normal",
+ "Subject": "hello",
+ "To": "@triggerBody()?['email']"
+ },
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['outlook']['connectionId']"
+ }
+ },
+ "method": "post",
+ "path": "/v2/Mail"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "$connections": {
+ "value": {
+ "outlook": {
+ "connectionId": "[parameters('connections_outlook_externalid')]",
+ "connectionName": "outlook",
+ "id": "[parameters('connections_outlook_id')]"
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+}
+TEMPLATE
+}
+```
+
+You can create the `outlook` API connection using Terraform, like the following.
+
+```hcl
+data "azurerm_managed_api" "managed_api_outlook" {
+ name = "outlook"
+ location = azurerm_resource_group.rg.location
+}
+
+resource "azurerm_api_connection" "api_connection_outlook" {
+ name = "outlook"
+ resource_group_name = azurerm_resource_group.rg.name
+ managed_api_id = data.azurerm_managed_api.managed_api_outlook.id
+ display_name = "outlook"
+}
+```
+
+Now you are ready to deploy Logic App using Terraform.
+
+```bash
+terraform init
+terraform plan -out tfplan
+terraform apply tfplan
+```
+
+Check the deployed resources.
+
+![](images/resources.png)
+
+Make sure to authenticate to the outlook API connection.
+
+![](images/authorize-outlook.png)
+
+## Test the workflow
+
+Go to Overview -> Run Trigger -> Run with payload.
+Then enter a JSON content with your email address.
+
+![](images/test-workflow.png)
+
+Within a second, you should get a success message. Then you can check your email to find out you received an email from the Logic App workflow.
+
+## Conclusion
+
+You learned in this lab how to export an ARM template for Logic App and use in Terraform.
\ No newline at end of file
diff --git a/140_logic_app/images/action-send-email.png b/140_logic_app/images/action-send-email.png
new file mode 100644
index 0000000..0d47376
Binary files /dev/null and b/140_logic_app/images/action-send-email.png differ
diff --git a/140_logic_app/images/authorize-outlook.png b/140_logic_app/images/authorize-outlook.png
new file mode 100644
index 0000000..9401aff
Binary files /dev/null and b/140_logic_app/images/authorize-outlook.png differ
diff --git a/140_logic_app/images/choose-action.png b/140_logic_app/images/choose-action.png
new file mode 100644
index 0000000..281e9e6
Binary files /dev/null and b/140_logic_app/images/choose-action.png differ
diff --git a/140_logic_app/images/resources.png b/140_logic_app/images/resources.png
new file mode 100644
index 0000000..687c4e5
Binary files /dev/null and b/140_logic_app/images/resources.png differ
diff --git a/140_logic_app/images/test-workflow.png b/140_logic_app/images/test-workflow.png
new file mode 100644
index 0000000..daf3b58
Binary files /dev/null and b/140_logic_app/images/test-workflow.png differ
diff --git a/140_logic_app/images/view-code-workflow.png b/140_logic_app/images/view-code-workflow.png
new file mode 100644
index 0000000..4a99609
Binary files /dev/null and b/140_logic_app/images/view-code-workflow.png differ
diff --git a/140_logic_app/images/view-code.png b/140_logic_app/images/view-code.png
new file mode 100644
index 0000000..8a41c28
Binary files /dev/null and b/140_logic_app/images/view-code.png differ
diff --git a/140_logic_app/logicapp.json b/140_logic_app/logicapp.json
new file mode 100644
index 0000000..3f85ff2
--- /dev/null
+++ b/140_logic_app/logicapp.json
@@ -0,0 +1,88 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "workflows_logic_app_name": {
+ "type": "String"
+ },
+ "connections_outlook_externalid": {
+ "type": "String"
+ },
+ "connections_outlook_id": {
+ "type": "String"
+ }
+ },
+ "variables": {},
+ "resources": [
+ {
+ "type": "Microsoft.Logic/workflows",
+ "apiVersion": "2017-07-01",
+ "name": "[parameters('workflows_logic_app_name')]",
+ "location": "westeurope",
+ "properties": {
+ "state": "Enabled",
+ "definition": {
+ "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "$connections": {
+ "defaultValue": {},
+ "type": "Object"
+ }
+ },
+ "triggers": {
+ "http-trigger": {
+ "type": "Request",
+ "kind": "Http",
+ "inputs": {
+ "schema": {
+ "properties": {
+ "email": {
+ "type": "string"
+ },
+ "sas_url": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ }
+ }
+ }
+ },
+ "actions": {
+ "action-send-email": {
+ "runAfter": {},
+ "type": "ApiConnection",
+ "inputs": {
+ "body": {
+ "Body": "Hello,
We are happy to tell you your video file is now available to download on this link: @{triggerBody()?['sas_url']}
Thank you,
",
+ "Importance": "Normal",
+ "Subject": "hello",
+ "To": "@triggerBody()?['email']"
+ },
+ "host": {
+ "connection": {
+ "name": "@parameters('$connections')['outlook']['connectionId']"
+ }
+ },
+ "method": "post",
+ "path": "/v2/Mail"
+ }
+ }
+ }
+ },
+ "parameters": {
+ "$connections": {
+ "value": {
+ "outlook": {
+ "connectionId": "[parameters('connections_outlook_externalid')]",
+ "connectionName": "outlook",
+ "id": "[parameters('connections_outlook_id')]"
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/140_logic_app/main.old.tf b/140_logic_app/main.old.tf
new file mode 100644
index 0000000..2671149
--- /dev/null
+++ b/140_logic_app/main.old.tf
@@ -0,0 +1,133 @@
+# resource "azurerm_resource_group" "rg" {
+# name = "rg-logic-app-demo"
+# location = "westeurope"
+# }
+
+# resource "azurerm_logic_app_workflow" "logic_app_email_sender" {
+# count = 0
+# name = "logic-app-demo-1357913"
+# location = azurerm_resource_group.rg.location
+# resource_group_name = azurerm_resource_group.rg.name
+
+# workflow_parameters = {
+# "$connections" = jsonencode({
+# type : "Object"
+# })
+# }
+# parameters = {
+# "$connections" = jsonencode({
+# outlook = {
+# connectionId = "${azurerm_api_connection.api_connection_outlook.id}"
+# connectionName = "outlook"
+# id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/providers/Microsoft.Web/locations/westeurope/managedApis/outlook"
+# }
+# })
+# }
+# # parameters = {
+# # "connections" = <Hello,
We are happy to tell you your video file is now available to download on this link:
Thank you,
",
+# "Importance": "Normal"
+# },
+# "path": "/v2/Mail"
+# }
+# }
+# BODY
+
+# }
+
+# data "azurerm_managed_api" "managed_api_outlook" {
+# name = "outlook"
+# location = azurerm_resource_group.rg.location
+# }
+
+# resource "azurerm_api_connection" "api_connection_outlook" {
+# name = "outlook"
+# resource_group_name = azurerm_resource_group.rg.name
+# managed_api_id = data.azurerm_managed_api.managed_api_outlook.id
+# display_name = "outlook"
+
+# # parameter_values = {
+# # connectionString = azurerm_servicebus_namespace.example.default_primary_connection_string
+# # }
+
+# # lifecycle {
+# # # NOTE: since the connectionString is a secure value it's not returned from the API
+# # ignore_changes = ["parameter_values"]
+# # }
+# }
diff --git a/140_logic_app/main.tf b/140_logic_app/main.tf
new file mode 100644
index 0000000..c260d21
--- /dev/null
+++ b/140_logic_app/main.tf
@@ -0,0 +1,30 @@
+resource "azurerm_resource_group" "rg" {
+ name = "rg-logic-app-demo"
+ location = "westeurope"
+}
+
+data "azurerm_managed_api" "managed_api_outlook" {
+ name = "outlook"
+ location = azurerm_resource_group.rg.location
+}
+
+resource "azurerm_api_connection" "api_connection_outlook" {
+ name = "outlook"
+ resource_group_name = azurerm_resource_group.rg.name
+ managed_api_id = data.azurerm_managed_api.managed_api_outlook.id
+ display_name = "outlook"
+}
+
+resource "azurerm_resource_group_template_deployment" "logic_app" {
+ name = "logic-app-deploy"
+ resource_group_name = azurerm_resource_group.rg.name
+ deployment_mode = "Incremental" # "Complete" #
+
+ parameters_content = jsonencode({
+ workflows_logic_app_name = { value = "logic-app-demo-135791555" }
+ connections_outlook_externalid = { value = azurerm_api_connection.api_connection_outlook.id }
+ connections_outlook_id = { value = data.azurerm_managed_api.managed_api_outlook.id }
+ })
+
+ template_content = file("logicapp.json")
+}
\ No newline at end of file
diff --git a/140_logic_app/providers.tf b/140_logic_app/providers.tf
new file mode 100644
index 0000000..b6b6468
--- /dev/null
+++ b/140_logic_app/providers.tf
@@ -0,0 +1,13 @@
+terraform {
+ required_providers {
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "3.75.0"
+ }
+ }
+}
+
+provider "azurerm" {
+ features {
+ }
+}
\ No newline at end of file
diff --git a/150_postgresql_flexible_server/postgresql_flexible_server.tf b/150_postgresql_flexible_server/postgresql_flexible_server.tf
new file mode 100644
index 0000000..87cf9f6
--- /dev/null
+++ b/150_postgresql_flexible_server/postgresql_flexible_server.tf
@@ -0,0 +1,56 @@
+resource "azurerm_resource_group" "rg" {
+ name = "rg-postgresql-flex-server"
+ location = "francecentral"
+}
+
+resource "azurerm_virtual_network" "vnet" {
+ name = "vnet-data"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ address_space = ["10.0.0.0/16"]
+}
+
+resource "azurerm_subnet" "subnet" {
+ name = "subnet-data"
+ resource_group_name = azurerm_resource_group.rg.name
+ virtual_network_name = azurerm_virtual_network.vnet.name
+ address_prefixes = ["10.0.2.0/24"]
+ service_endpoints = ["Microsoft.Storage"]
+
+ delegation {
+ name = "fs"
+ service_delegation {
+ name = "Microsoft.DBforPostgreSQL/flexibleServers"
+ actions = [
+ "Microsoft.Network/virtualNetworks/subnets/join/action",
+ ]
+ }
+ }
+}
+
+resource "azurerm_private_dns_zone" "private_dns_zone" {
+ name = "hcorp.postgres.database.azure.com"
+ resource_group_name = azurerm_resource_group.rg.name
+}
+
+resource "azurerm_private_dns_zone_virtual_network_link" "dns-zone-vnet-link" {
+ name = "dns-zone-vnet-link"
+ private_dns_zone_name = azurerm_private_dns_zone.private_dns_zone.name
+ virtual_network_id = azurerm_virtual_network.vnet.id
+ resource_group_name = azurerm_resource_group.rg.name
+}
+
+resource "azurerm_postgresql_flexible_server" "postgres" {
+ name = "postgresql-flexserver-hcorp"
+ resource_group_name = azurerm_resource_group.rg.name
+ location = azurerm_resource_group.rg.location
+ version = "12"
+ delegated_subnet_id = azurerm_subnet.subnet.id
+ private_dns_zone_id = azurerm_private_dns_zone.private_dns_zone.id
+ administrator_login = "azureadmin"
+ administrator_password = "@Aa123456789"
+ zone = "1"
+ storage_mb = 32768
+ sku_name = "GP_Standard_D4s_v3"
+ depends_on = [azurerm_private_dns_zone_virtual_network_link.dns-zone-vnet-link]
+}
diff --git a/150_postgresql_flexible_server/providers.tf b/150_postgresql_flexible_server/providers.tf
new file mode 100644
index 0000000..92547fc
--- /dev/null
+++ b/150_postgresql_flexible_server/providers.tf
@@ -0,0 +1,13 @@
+terraform {
+ required_providers {
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "3.78.0"
+ }
+ }
+}
+
+provider "azurerm" {
+ features {
+ }
+}
\ No newline at end of file
diff --git a/151_mysql_flexible_server/mysql_flexible_server.tf b/151_mysql_flexible_server/mysql_flexible_server.tf
new file mode 100644
index 0000000..cf8751e
--- /dev/null
+++ b/151_mysql_flexible_server/mysql_flexible_server.tf
@@ -0,0 +1,82 @@
+resource "azurerm_resource_group" "rg" {
+ name = "rg-mysql-flexible-server"
+ location = "francecentral"
+}
+
+resource "azurerm_virtual_network" "vnet" {
+ name = "vnet-data"
+ location = azurerm_resource_group.rg.location
+ resource_group_name = azurerm_resource_group.rg.name
+ address_space = ["10.0.0.0/16"]
+}
+
+resource "azurerm_subnet" "subnet" {
+ name = "subnet-data"
+ resource_group_name = azurerm_resource_group.rg.name
+ virtual_network_name = azurerm_virtual_network.vnet.name
+ address_prefixes = ["10.0.2.0/24"]
+ service_endpoints = ["Microsoft.Storage"]
+
+ delegation {
+ name = "fs"
+ service_delegation {
+ name = "Microsoft.DBforMySQL/flexibleServers"
+ actions = [
+ "Microsoft.Network/virtualNetworks/subnets/join/action",
+ ]
+ }
+ }
+}
+
+resource "azurerm_private_dns_zone" "private_dns_zone" {
+ name = "hcorp.mysql.database.azure.com"
+ resource_group_name = azurerm_resource_group.rg.name
+}
+
+resource "azurerm_private_dns_zone_virtual_network_link" "dns-zone-vnet-link" {
+ name = "dns-zone-vnet-link"
+ private_dns_zone_name = azurerm_private_dns_zone.private_dns_zone.name
+ virtual_network_id = azurerm_virtual_network.vnet.id
+ resource_group_name = azurerm_resource_group.rg.name
+}
+
+resource "azurerm_mysql_flexible_server" "mysql" {
+ name = "mysql-flexserver-hcorp"
+ resource_group_name = azurerm_resource_group.rg.name
+ location = azurerm_resource_group.rg.location
+ version = "8.0.21" # 5.7
+ delegated_subnet_id = azurerm_subnet.subnet.id
+ private_dns_zone_id = azurerm_private_dns_zone.private_dns_zone.id
+ administrator_login = "azureadmin"
+ administrator_password = "@Aa123456789"
+ zone = "1"
+ sku_name = "GP_Standard_B1ms" # Standard_D2ads_v5
+ backup_retention_days = 1 # between 1 and 35, Defaults to 7
+
+ high_availability {
+ mode = "ZoneRedundant"
+ standby_availability_zone = "2"
+ }
+
+ maintenance_window {
+ day_of_week = 0
+ start_hour = 8
+ start_minute = 0
+ }
+
+ storage {
+ iops = 360
+ size_gb = 20
+ }
+
+ depends_on = [azurerm_private_dns_zone_virtual_network_link.dns-zone-vnet-link]
+}
+
+# Manages the MySQL Flexible Server Database
+resource "azurerm_mysql_flexible_database" "db" {
+ charset = "utf8mb4"
+ collation = "utf8mb4_unicode_ci"
+ name = "mysqlfsdb"
+ resource_group_name = azurerm_resource_group.rg.name
+ server_name = azurerm_mysql_flexible_server.mysql.name
+}
\ No newline at end of file
diff --git a/151_mysql_flexible_server/providers.tf b/151_mysql_flexible_server/providers.tf
new file mode 100644
index 0000000..92547fc
--- /dev/null
+++ b/151_mysql_flexible_server/providers.tf
@@ -0,0 +1,13 @@
+terraform {
+ required_providers {
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "3.78.0"
+ }
+ }
+}
+
+provider "azurerm" {
+ features {
+ }
+}
\ No newline at end of file
diff --git a/160_azure_devops_project/README.md b/160_azure_devops_project/README.md
new file mode 100644
index 0000000..ce711a8
--- /dev/null
+++ b/160_azure_devops_project/README.md
@@ -0,0 +1,49 @@
+# Deploying Azure DevOps project using Terraform
+
+## Introduction
+This document explains the [Terraform](https://www.terraform.io/) template used to define and provide the necessary data for an [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) project. Terraform is an Infrastructure as Code (IaC) tool that allows you to build, change, and version infrastructure safely and efficiently. This template includes definitions for various resources, such as the project itself, Git repositories, variable groups, build definitions (pipelines), and work items.
+
+## Explanation
+The template begins by defining an `azuredevops_project` resource named "Project demo TF". This is a private project that uses Git as its version control system and follows the Agile work item template.
+
+Next, the `azuredevops_git_repository` is defined. This is the Git repository for the project, named "repo-webapp". It is initialized as a clean repository, meaning it will not have any commits or branches initially.
+
+The `azuredevops_variable_group` is defined next. This is a group of variables that can be used throughout the project. The variable group includes a single variable, FOO, with the value of BAR.
+
+An `azuredevops_build_definition` resource, named "CI-CD Pipeline", is defined next. This is a Continuous Integration/Continuous Deployment (CI/CD) pipeline for the project. It includes a schedule for when the pipeline should run, which repositories it should include, and which variable groups it should use.
+
+Work items are defined next. These are tasks that need to be completed for the project. In this template, three types of work items are created: an issue, a user story, and a task.
+
+Finally, the template ends with the definition of another `azuredevops_git_repository` and `azuredevops_build_definition`. These are used to import an existing Git repository into the project and to define a pipeline for this imported repository.
+
+## Deploying the terraform template
+
+Create PAT token in Azure DevOps.
+Replace it in `providers.tf` file.
+
+```bash
+terraform init
+
+terraform plan -out tfplan
+
+terraform apply tfplan
+
+terraform destroy
+```
+
+## Deployed resources
+
+Items created by Terraform provider.
+![](images/work-items.png)
+
+Repositories created or imported by Terraform.
+![](images/repositories.png)
+
+Pipelines created or imported by Terraform.
+![](images/pipelines.png)
+
+Variable groups created by Terraform.
+![](images/variable-groups.png)
+
+## Conclusion
+In conclusion, this Terraform template automates the creation and configuration of an Azure DevOps project and its related resources. By using this template, you can ensure that your infrastructure is consistent and repeatable, reducing the potential for human error and making it easier to manage and maintain your project. For more information about Terraform, visit the [official Terraform documentation](https://www.terraform.io/docs/index.html). For more information about Azure DevOps, visit the [official Azure DevOps documentation](https://docs.microsoft.com/en-us/azure/devops/?view=azure-devops).
\ No newline at end of file
diff --git a/160_azure_devops_project/commands.sh b/160_azure_devops_project/commands.sh
new file mode 100644
index 0000000..f7820ca
--- /dev/null
+++ b/160_azure_devops_project/commands.sh
@@ -0,0 +1,10 @@
+# Create PAT token in Azure DevOps
+# Replace it in providers.tf file
+
+terraform init
+
+terraform plan -out tfplan
+
+terraform apply tfplan
+
+terraform destroy
\ No newline at end of file
diff --git a/160_azure_devops_project/images/pipelines.png b/160_azure_devops_project/images/pipelines.png
new file mode 100644
index 0000000..ab17e5f
Binary files /dev/null and b/160_azure_devops_project/images/pipelines.png differ
diff --git a/160_azure_devops_project/images/repositories.png b/160_azure_devops_project/images/repositories.png
new file mode 100644
index 0000000..4c7490f
Binary files /dev/null and b/160_azure_devops_project/images/repositories.png differ
diff --git a/160_azure_devops_project/images/variable-groups.png b/160_azure_devops_project/images/variable-groups.png
new file mode 100644
index 0000000..08b8424
Binary files /dev/null and b/160_azure_devops_project/images/variable-groups.png differ
diff --git a/160_azure_devops_project/images/work-items.png b/160_azure_devops_project/images/work-items.png
new file mode 100644
index 0000000..36c1831
Binary files /dev/null and b/160_azure_devops_project/images/work-items.png differ
diff --git a/160_azure_devops_project/main.tf b/160_azure_devops_project/main.tf
new file mode 100644
index 0000000..52beba8
--- /dev/null
+++ b/160_azure_devops_project/main.tf
@@ -0,0 +1,130 @@
+resource "azuredevops_project" "project" {
+ name = "Project demo TF"
+ visibility = "private"
+ version_control = "Git"
+ work_item_template = "Agile"
+}
+
+resource "azuredevops_git_repository" "repo" {
+ name = "repo-webapp"
+ project_id = azuredevops_project.project.id
+ default_branch = "refs/heads/main"
+
+ initialization {
+ init_type = "Clean"
+ }
+}
+
+resource "azuredevops_variable_group" "var_group" {
+ project_id = azuredevops_project.project.id
+ name = "Variable Group for WebApp"
+ description = "Managed by Terraform"
+ allow_access = true
+
+ variable {
+ name = "FOO"
+ value = "BAR"
+ }
+}
+
+resource "azuredevops_build_definition" "pipeline" {
+ project_id = azuredevops_project.project.id
+ name = "CI-CD Pipeline"
+ path = "\\webapp"
+
+ ci_trigger {
+ use_yaml = false
+ }
+
+ schedules {
+ branch_filter {
+ include = ["main"]
+ exclude = ["test", "regression"]
+ }
+ days_to_build = ["Wed", "Sun"]
+ schedule_only_with_changes = true
+ start_hours = 10
+ start_minutes = 59
+ time_zone = "(UTC) Coordinated Universal Time"
+ }
+
+ repository {
+ repo_type = "TfsGit"
+ repo_id = azuredevops_git_repository.repo.id
+ branch_name = azuredevops_git_repository.repo.default_branch
+ yml_path = "azure-pipelines.yml"
+ }
+
+ variable_groups = [
+ azuredevops_variable_group.var_group.id
+ ]
+
+ variable {
+ name = "PipelineVariable"
+ value = "Go Microsoft!"
+ }
+
+ variable {
+ name = "PipelineSecret"
+ secret_value = "ZGV2cw"
+ is_secret = true
+ }
+}
+
+resource "azuredevops_workitem" "issue" {
+ project_id = azuredevops_project.project.id
+ title = "Example Work Item"
+ type = "Issue" # Bug, Epic, Feature, Issue, Task, Test Case, User Story
+ state = "Active" # New, Active, Resolved, and Closed
+ tags = ["prod"]
+}
+
+resource "azuredevops_workitem" "user-story" {
+ project_id = azuredevops_project.project.id
+ title = "Creating Login Page"
+ type = "User Story" # Bug, Epic, Feature, Issue, Task, Test Case, User Story
+ state = "Active" # New, Active, Resolved, and Closed
+ tags = ["frontend", "mobile-app"]
+}
+
+resource "azuredevops_workitem" "task" {
+ project_id = azuredevops_project.project.id
+
+ title = "Creating Authorisation Server"
+ type = "Task" # Bug, Epic, Feature, Issue, Task, Test Case, User Story
+ tags = ["api", "authorisation"]
+}
+
+# import existing Git repository
+
+resource "azuredevops_git_repository" "import-repo" {
+ project_id = azuredevops_project.project.id
+ name = "Imported Repository"
+ initialization {
+ init_type = "Import"
+ source_type = "Git"
+ source_url = "https://github.com/HoussemDellai/azure-devops-pipelines-samples"
+ }
+}
+
+resource "azuredevops_build_definition" "pipeline-imported" {
+ project_id = azuredevops_project.project.id
+ name = "CI-CD Pipeline Matrix"
+ path = "\\webapp"
+ agent_pool_name = "Azure Pipelines"
+
+ ci_trigger {
+ use_yaml = true
+ }
+
+ features {
+ skip_first_run = false
+ }
+
+ repository {
+ repo_type = "TfsGit"
+ repo_id = azuredevops_git_repository.import-repo.id
+ branch_name = azuredevops_git_repository.import-repo.default_branch
+ yml_path = "azure-pipelines-matrix.yaml"
+ }
+}
diff --git a/160_azure_devops_project/providers.tf b/160_azure_devops_project/providers.tf
new file mode 100644
index 0000000..668b9e3
--- /dev/null
+++ b/160_azure_devops_project/providers.tf
@@ -0,0 +1,17 @@
+terraform {
+ required_providers {
+ azuredevops = {
+ source = "microsoft/azuredevops"
+ version = "0.10.0"
+ }
+ }
+}
+
+provider "azuredevops" {
+ # Configuration options
+ org_service_url = "https://dev.azure.com/houssemdellai"
+ personal_access_token = "qkfon5cdldekin4qnkgfr2nf367h6yjnndm5upwqepd5rekl4l5a"
+}
+
+# export AZDO_PERSONAL_ACCESS_TOKEN=
+# export AZDO_ORG_SERVICE_URL=https://dev.azure.com/
diff --git a/29_map_list/main.tf b/29_map_list/main.tf
new file mode 100644
index 0000000..96dedd9
--- /dev/null
+++ b/29_map_list/main.tf
@@ -0,0 +1,125 @@
+variable "cidr" {
+ # type = map
+ default = {
+ "prod" = ["10.7.3.0/24", "10.7.4.0/24"]
+ "test" = ["10.8.3.0/24", "10.8.4.0/24"]
+ }
+}
+
+output "prod_cidr" {
+ value = var.cidr["prod"]
+}
+
+variable "subnets" {
+ default = {
+ "AzureFirewallSubnet" = {
+ "address_prefixes" = [
+ "10.0.0.64/26",
+ ]
+ "delegation" = []
+ "enforce_private_link_endpoint_network_policies" = false
+ "enforce_private_link_service_network_policies" = false
+ "id" = "/subscriptions/xxxxxxxxxxxxxxxxx-5dddd9fa8910/resourceGroups/rg-lzaaca-hub-dev-neu/providers/Microsoft.Network/virtualNetworks/vnet-dev-neu-hub/subnets/AzureFirewallSubnet"
+ "name" = "AzureFirewallSubnet"
+ "private_endpoint_network_policies_enabled" = true
+ "private_link_service_network_policies_enabled" = true
+ "resource_group_name" = "rg-lzaaca-hub-dev-neu"
+ "service_endpoint_policy_ids" = []
+ "service_endpoints" = []
+ "timeouts" = null /* object */
+ "virtual_network_name" = "vnet-dev-neu-hub"
+ }
+ "GatewaySubnet" = {
+ "address_prefixes" = [
+ "10.0.0.0/27",
+ ]
+ "delegation" = []
+ "enforce_private_link_endpoint_network_policies" = false
+ "enforce_private_link_service_network_policies" = false
+ "id" = "/subscriptions/xxxxxxxxxxxxxxxxx-5dddd9fa8910/resourceGroups/rg-lzaaca-hub-dev-neu/providers/Microsoft.Network/virtualNetworks/vnet-dev-neu-hub/subnets/GatewaySubnet"
+ "name" = "GatewaySubnet"
+ "private_endpoint_network_policies_enabled" = true
+ "private_link_service_network_policies_enabled" = true
+ "resource_group_name" = "rg-lzaaca-hub-dev-neu"
+ "service_endpoint_policy_ids" = []
+ "service_endpoints" = []
+ "timeouts" = null /* object */
+ "virtual_network_name" = "vnet-dev-neu-hub"
+ }
+ }
+}
+
+output "subnetFirewall" {
+ value = var.subnets["AzureFirewallSubnet"]
+}
+
+output "subnetFirewallId" {
+ value = var.subnets["AzureFirewallSubnet"].id
+}
+
+variable "subnets-1-2" {
+ default = [
+ {
+ name = "subnet-1"
+ addressPrefixes = ["10.0.1.0/24"]
+ },
+ {
+ name = "subnet-2"
+ addressPrefixes = ["10.0.2.0/24"]
+ }
+ ]
+}
+
+variable "subnets-3-4" {
+ default = [
+ {
+ name = "subnet-3"
+ addressPrefixes = ["10.0.3.0/24"]
+ service_delegation = [{
+ name = "Microsoft.App/environments"
+ actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"]
+ }]
+ },
+ {
+ name = "subnet-4"
+ addressPrefixes = ["10.0.4.0/24"]
+ }
+ ]
+}
+
+variable "subnet-5" {
+ default = {
+ name = "subnet-5"
+ addressPrefixes = ["10.0.5.0/24"]
+ }
+}
+
+output "subnetsConcat" {
+ value = concat(var.subnets-1-2, var.subnets-3-4, [var.subnet-5])
+}
+
+output "subnetsFlatten" {
+ value = flatten([var.subnets-1-2, var.subnets-3-4, [var.subnet-5]])
+}
+
+# output "subnetMerge" {
+# value = merge(var.subnets-1-2, var.subnets-3-4, [var.subnet-5])
+# }
+
+output "subnetsConditional" {
+ value = "true" != "true" ? concat(var.subnets-1-2, [
+ {
+ name = "subnet-6"
+ addressPrefixes = ["10.0.6.0/24"]
+ },
+ {
+ name = "subnet-7"
+ addressPrefixes = ["10.0.7.0/24"]
+ # other = "other"
+ }
+ ]) : var.subnets-1-2
+}
+
+output "concat_with_null" {
+ value = concat(var.subnets-1-2, [null])
+}
\ No newline at end of file
diff --git a/93_import_terraform/README.md b/93_import_terraform/README.md
new file mode 100644
index 0000000..f46ffa7
--- /dev/null
+++ b/93_import_terraform/README.md
@@ -0,0 +1,116 @@
+# Terraform import feature
+
+## Introduction
+
+You will learn in this demo how to import existing infrastructure into terraform configuration.
+
+## How it works ?
+
+To import an existing resource group for example, you will define an import block like following.
+
+```hcl
+import {
+ id = "/subscriptions/82f6d75e-xxx/resourceGroups/rg-terraform"
+ to = azurerm_resource_group.main
+}
+```
+
+You will need the resource ID for the resource to import, for the ``id` parameter.
+And you will need to specify the terraform resource type and name within the `to` parameter.
+
+Then you run the terraform commands to initialize and generate the configuration.
+
+```bash
+terraform init
+
+terraform plan -generate-config-out="generated.tf"
+```
+
+That will generate a ``generated.tf` file with the terraform configuration, like following.
+
+```hcl
+# __generated__ by Terraform from "/subscriptions/xxx/resourceGroups/rg-terraform"
+resource "azurerm_resource_group" "main" {
+ location = "westeurope"
+ managed_by = null
+ name = "rg-terraform"
+ tags = {}
+}
+```
+
+Let's do the same with an `Azure Key vault` and a secret.
+
+```hcl
+import {
+ id = "/subscriptions/82f6d75e-xxx/resourceGroups/rg-terraform/providers/Microsoft.KeyVault/vaults/kv12357913tf01"
+ to = azurerm_key_vault.main
+}
+
+import {
+ id = "https://kv12357913tf.vault.azure.net/secrets/terraform-backend-key/1ddace06fdbe4400a357474255564a2e"
+ to = azurerm_key_vault_secret.main
+}
+```
+
+Run the command to generate the configuration.
+```bash
+terraform plan -generate-config-out="generated.tf"
+```
+
+The generated configuration is the following.
+
+```hcl
+# __generated__ by Terraform
+resource "azurerm_key_vault" "main" {
+ access_policy = [{
+ application_id = ""
+ certificate_permissions = ["all"]
+ key_permissions = ["all"]
+ object_id = "99b281c9-823c-4633-af92-8ac556a19bee"
+ secret_permissions = ["all"]
+ storage_permissions = ["all"]
+ tenant_id = "16b3c013-xxxxx"
+ }]
+ enable_rbac_authorization = false
+ enabled_for_deployment = false
+ enabled_for_disk_encryption = false
+ enabled_for_template_deployment = false
+ location = "westeurope"
+ name = "kv12357913tf01"
+ public_network_access_enabled = true
+ purge_protection_enabled = false
+ resource_group_name = "rg-terraform"
+ sku_name = "standard"
+ soft_delete_retention_days = 90
+ tags = {}
+ tenant_id = "16b3c013-xxx"
+ network_acls {
+ bypass = "AzureServices"
+ default_action = "Allow"
+ ip_rules = []
+ virtual_network_subnet_ids = []
+ }
+}
+
+# __generated__ by Terraform
+resource "azurerm_key_vault_secret" "main" {
+ content_type = null
+ expiration_date = null
+ key_vault_id = "/subscriptions/82f6d75e-xxx/resourceGroups/rg-terraform/providers/Microsoft.KeyVault/vaults/kv12357913tf"
+ name = "terraform-backend-key"
+ not_before_date = null
+ tags = {
+ file-encoding = "utf-8"
+ }
+ value = null # sensitive
+}
+```
+
+> Note how terraform didn't import the value of the secret.
+
+You can export many other resources from Azure.
+Check out the `commands.sh` script to create resources in Azure and test terraform import to see how it works with each resource.
+
+## Conclusion
+
+Terraform import block is still in preview, but you would expect it to get even more mature with more advanced features.
\ No newline at end of file
diff --git a/93_import_terraform/commands.sh b/93_import_terraform/commands.sh
new file mode 100644
index 0000000..38354ac
--- /dev/null
+++ b/93_import_terraform/commands.sh
@@ -0,0 +1,129 @@
+# create resource group
+$rg_id=$(az group create --name rg-terraform --location westeurope --query id --output tsv)
+
+# create storage account
+$storage_account_id=(az storage account create --name tfsa123579 --resource-group rg-terraform --location westeurope --sku Standard_LRS --query id --output tsv)
+
+# create container
+$container_id=(az storage container create --name tfstate --account-name tfsa123579 --auth-mode login --query id --output tsv)
+
+# create function app
+$function_id=(az functionapp create --resource-group rg-terraform --consumption-plan-location westeurope --name functionapp-terraform --storage-account tfsa123579 --runtime node --query id --output tsv)
+
+# create key vault
+$keyvault_id=(az keyvault create --resource-group rg-terraform --name kv12357913tf01 --location westeurope --query id --output tsv)
+
+# create key vault secret
+$keyvault_secret_id=(az keyvault secret set --vault-name kv12357913tf --name terraform-backend-key --value "terraform-backend-key" --query id --output tsv)
+
+# create vm
+$vm_id=(az vm create --resource-group rg-terraform --name vm-terraform --image UbuntuLTS --admin-username azureuser --generate-ssh-keys --query id --output tsv)
+
+# create app service plan
+$appservice_plan_id=(az appservice plan create --resource-group rg-terraform --name asp-terraform --sku B1 --is-linux --query id --output tsv)
+
+# create app service
+$appservice_id=(az webapp create --resource-group rg-terraform --plan asp-terraform --name webapp-terraform --runtime "java:11:JavaSE:11" --query id --output tsv)
+
+# create container app environment
+$containerapp_environment_id=(az containerapp env create --resource-group rg-terraform --name containerapp-terraform --location westeurope --query id --output tsv)
+
+# create container app
+$containerapp_id=(az containerapp create --resource-group rg-terraform --name containerapp-terraform --environment containerapp-terraform --cpu 0.25 --memory 0.5 --image nginx --query id --output tsv)
+
+# create AKS cluster
+$aks_id=(az aks create --resource-group rg-terraform --name aks-terraform --node-count 1 --query id --output tsv)
+
+# create AKS cluster with node pool
+$aks_nodepool=(az aks nodepool add --resource-group rg-terraform --cluster-name aks-terraform --node-count 1 --node-vm-size Standard_B1s --name npapps --node-count 1 --node-vm-size Standard_B2s --query id --output tsv)
+
+# # create vnet
+# $vnet_id=(az network vnet create --resource-group rg-terraform --name vnet-terraform --address-prefixes ["10.0.0.0/8"] --query id --output tsv)
+
+# # create subnet
+# $subnet_id="{$vnet_id}/subnets/subnet-terraform"
+
+@"
+import {
+ id = "$rg_id"
+ to = azurerm_resource_group.main
+}
+
+import {
+ id = "$storage_account_id"
+ to = azurerm_storage_account.main
+}
+
+import {
+ id = "$container_id"
+ to = azurerm_storage_container.main
+}
+
+import {
+ id = "$function_id"
+ to = azurerm_function_app.main
+}
+
+import {
+ id = "$keyvault_id"
+ to = azurerm_key_vault.main
+}
+
+import {
+ id = "$keyvault_secret_id"
+ to = azurerm_key_vault_secret.main
+}
+
+import {
+ id = "$vm_id"
+ to = azurerm_virtual_machine.main
+}
+
+import {
+ id = "$appservice_plan_id"
+ to = azurerm_app_service_plan.main
+}
+
+import {
+ id = "$appservice_id"
+ to = azurerm_app_service.main
+}
+
+import {
+ id = "$containerapp_environment_id"
+ to = azurerm_container_app_environment.main
+}
+
+import {
+ id = "$containerapp_id"
+ to = azurerm_container_app.main
+}
+
+import {
+ id = "$aks_id"
+ to = azurerm_kubernetes_cluster.main
+}
+
+import {
+ id = "$aks_nodepool"
+ to = azurerm_kubernetes_cluster_node_pool.main
+}
+
+# import {
+# id = "$vnet_id"
+# to = azurerm_virtual_network.main
+# }
+
+# import {
+# id = "$subnet_id"
+# to = azurerm_subnet.main
+# }
+
+"@ > main.tf
+
+
+# start importing existing resources to terraform configuration
+
+terraform init
+
+terraform plan -generate-config-out="generated.tf"
\ No newline at end of file
diff --git a/93_import_terraform/generated.tf b/93_import_terraform/generated.tf
new file mode 100644
index 0000000..9a61261
--- /dev/null
+++ b/93_import_terraform/generated.tf
@@ -0,0 +1,380 @@
+# __generated__ by Terraform
+# Please review these resources and move them into your main configuration files.
+
+# __generated__ by Terraform from "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform"
+resource "azurerm_resource_group" "main" {
+ location = "westeurope"
+ managed_by = null
+ name = "rg-terraform"
+ tags = {}
+}
+
+# __generated__ by Terraform
+resource "azurerm_container_app_environment" "main" {
+ dapr_application_insights_connection_string = null # sensitive
+ infrastructure_subnet_id = null
+ internal_load_balancer_enabled = false
+ location = "westeurope"
+ log_analytics_workspace_id = null
+ name = "containerapp-terraform"
+ resource_group_name = "rg-terraform"
+ tags = {}
+ zone_redundancy_enabled = false
+}
+
+# __generated__ by Terraform from "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.Web/serverfarms/asp-terraform"
+resource "azurerm_app_service_plan" "main" {
+ app_service_environment_id = null
+ is_xenon = false
+ kind = "linux"
+ location = "westeurope"
+ maximum_elastic_worker_count = 1
+ name = "asp-terraform"
+ per_site_scaling = false
+ reserved = true
+ resource_group_name = "rg-terraform"
+ tags = {}
+ zone_redundant = false
+ sku {
+ capacity = 1
+ size = "B1"
+ tier = "Basic"
+ }
+}
+
+# __generated__ by Terraform from "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.Compute/virtualMachines/vm-terraform"
+resource "azurerm_virtual_machine" "main" {
+ availability_set_id = null
+ delete_data_disks_on_termination = null
+ delete_os_disk_on_termination = null
+ license_type = null
+ location = "westeurope"
+ name = "vm-terraform"
+ network_interface_ids = ["/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.Network/networkInterfaces/vm-terraformVMNic"]
+ primary_network_interface_id = null
+ proximity_placement_group_id = null
+ resource_group_name = "rg-terraform"
+ tags = {}
+ vm_size = "Standard_DS1_v2"
+ zones = []
+ os_profile {
+ admin_password = null # sensitive
+ admin_username = "azureuser"
+ computer_name = "vm-terraform"
+ custom_data = null
+ }
+ os_profile_linux_config {
+ disable_password_authentication = true
+ ssh_keys {
+ key_data = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2SXD2alKvY6vixbgpxULdP/EIxTNccqvZTEO1sfcaswOPmUFxaO87JnJ4w+w446vQb/D847KJFxNql5OvLAFaoV1JT4YvrWw6D63wtZt/VwoTq5XDPnNFD8C6Ui4uwMCY2ZLcgg5suTCfe4lbyYWKkpFtFYK51eWrDA8UK3ndWFpwSgwR+FMDJ2qhCeMBSMSIVn6sAdPOvba1+gDQ1ELT8aLCXcdpnmY8yU/srj4b9qIGkZ01/s1OmJnUU3pdv41ygb4WB9rArGjWjl4+y9en6s4kPsUuUveij7+av7RqMwcy3Pb/61usoUILO9vahIe9T5PGsO7hPAKOkxEUIvEZ"
+ path = "/home/azureuser/.ssh/authorized_keys"
+ }
+ }
+ storage_image_reference {
+ id = null
+ offer = "UbuntuServer"
+ publisher = "Canonical"
+ sku = "18.04-LTS"
+ version = "latest"
+ }
+ storage_os_disk {
+ caching = "ReadWrite"
+ create_option = "FromImage"
+ disk_size_gb = 30
+ image_uri = null
+ managed_disk_id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.Compute/disks/vm-terraform_disk1_7db2f8d581d1456ba39aa16c39ea35b7"
+ managed_disk_type = "Premium_LRS"
+ name = "vm-terraform_disk1_7db2f8d581d1456ba39aa16c39ea35b7"
+ os_type = "Linux"
+ vhd_uri = null
+ write_accelerator_enabled = false
+ }
+}
+
+# __generated__ by Terraform from "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.App/containerApps/containerapp-terraform"
+resource "azurerm_container_app" "main" {
+ container_app_environment_id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.App/managedEnvironments/containerapp-terraform"
+ name = "containerapp-terraform"
+ resource_group_name = "rg-terraform"
+ revision_mode = "Single"
+ tags = {}
+ template {
+ max_replicas = 10
+ min_replicas = 0
+ revision_suffix = null
+ container {
+ args = []
+ command = []
+ cpu = 0.25
+ image = "nginx"
+ memory = "0.5Gi"
+ name = "containerapp-terraform"
+ }
+ }
+}
+
+# __generated__ by Terraform
+resource "azurerm_app_service" "main" {
+ app_service_plan_id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.Web/serverfarms/asp-terraform"
+ app_settings = {}
+ client_affinity_enabled = true
+ client_cert_enabled = false
+ client_cert_mode = "Required"
+ enabled = true
+ https_only = false
+ key_vault_reference_identity_id = "SystemAssigned"
+ location = "westeurope"
+ name = "webapp-terraform"
+ resource_group_name = "rg-terraform"
+ tags = {}
+ auth_settings {
+ additional_login_params = {}
+ allowed_external_redirect_urls = []
+ default_provider = null
+ enabled = false
+ issuer = null
+ runtime_version = null
+ token_refresh_extension_hours = 0
+ token_store_enabled = false
+ unauthenticated_client_action = null
+ }
+ logs {
+ detailed_error_messages_enabled = false
+ failed_request_tracing_enabled = false
+ application_logs {
+ file_system_level = "Off"
+ }
+ http_logs {
+ }
+ }
+ site_config {
+ acr_use_managed_identity_credentials = false
+ acr_user_managed_identity_client_id = null
+ always_on = false
+ app_command_line = null
+ auto_swap_slot_name = null
+ default_documents = ["Default.htm", "Default.html", "Default.asp", "index.htm", "index.html", "iisstart.htm", "default.aspx", "index.php", "hostingstart.html"]
+ dotnet_framework_version = "v4.0"
+ ftps_state = "FtpsOnly"
+ health_check_path = null
+ http2_enabled = true
+ ip_restriction = []
+ java_container = null
+ java_container_version = null
+ java_version = null
+ linux_fx_version = "DOTNETCORE|8.0"
+ local_mysql_enabled = false
+ managed_pipeline_mode = "Integrated"
+ min_tls_version = "1.2"
+ number_of_workers = 1
+ php_version = null
+ python_version = null
+ remote_debugging_enabled = false
+ remote_debugging_version = null
+ scm_ip_restriction = []
+ scm_type = "None"
+ scm_use_main_ip_restriction = false
+ use_32_bit_worker_process = true
+ vnet_route_all_enabled = false
+ websockets_enabled = false
+ windows_fx_version = null
+ }
+ source_control {
+ branch = "main"
+ manual_integration = false
+ repo_url = null
+ rollback_enabled = false
+ use_mercurial = false
+ }
+}
+
+# __generated__ by Terraform from "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.ContainerService/managedClusters/aks-terraform/agentPools/npapps"
+resource "azurerm_kubernetes_cluster_node_pool" "main" {
+ capacity_reservation_group_id = null
+ custom_ca_trust_enabled = false
+ enable_auto_scaling = false
+ enable_host_encryption = false
+ enable_node_public_ip = false
+ eviction_policy = null
+ fips_enabled = false
+ host_group_id = null
+ kubelet_disk_type = "OS"
+ kubernetes_cluster_id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.ContainerService/managedClusters/aks-terraform"
+ max_count = 0
+ max_pods = 110
+ message_of_the_day = null
+ min_count = 0
+ mode = "User"
+ name = "npapps"
+ node_count = 1
+ node_labels = {}
+ node_public_ip_prefix_id = null
+ node_taints = []
+ orchestrator_version = "1.26.6"
+ os_disk_size_gb = 128
+ os_disk_type = "Managed"
+ os_sku = "Ubuntu"
+ os_type = "Linux"
+ pod_subnet_id = null
+ priority = "Regular"
+ proximity_placement_group_id = null
+ scale_down_mode = "Delete"
+ snapshot_id = null
+ spot_max_price = -1
+ tags = {}
+ ultra_ssd_enabled = false
+ vm_size = "Standard_B2s"
+ vnet_subnet_id = null
+ workload_runtime = "OCIContainer"
+ zones = []
+}
+
+# __generated__ by Terraform
+resource "azurerm_key_vault" "main" {
+ access_policy = [{
+ application_id = ""
+ certificate_permissions = ["all"]
+ key_permissions = ["all"]
+ object_id = "99b281c9-823c-4633-af92-8ac556a19bee"
+ secret_permissions = ["all"]
+ storage_permissions = ["all"]
+ tenant_id = "16b3c013-d300-468d-ac64-7eda0820b6d3"
+ }]
+ enable_rbac_authorization = false
+ enabled_for_deployment = false
+ enabled_for_disk_encryption = false
+ enabled_for_template_deployment = false
+ location = "westeurope"
+ name = "kv12357913tf01"
+ public_network_access_enabled = true
+ purge_protection_enabled = false
+ resource_group_name = "rg-terraform"
+ sku_name = "standard"
+ soft_delete_retention_days = 90
+ tags = {}
+ tenant_id = "16b3c013-d300-468d-ac64-7eda0820b6d3"
+ network_acls {
+ bypass = "AzureServices"
+ default_action = "Allow"
+ ip_rules = []
+ virtual_network_subnet_ids = []
+ }
+}
+
+# __generated__ by Terraform
+resource "azurerm_kubernetes_cluster" "main" {
+ automatic_channel_upgrade = null
+ azure_policy_enabled = null
+ custom_ca_trust_certificates_base64 = []
+ disk_encryption_set_id = null
+ dns_prefix = "aks-terraf-rg-terraform-82f6d7"
+ dns_prefix_private_cluster = null
+ edge_zone = null
+ http_application_routing_enabled = null
+ image_cleaner_enabled = null
+ image_cleaner_interval_hours = null
+ kubernetes_version = "1.26.6"
+ local_account_disabled = false
+ location = "westeurope"
+ name = "aks-terraform"
+ node_os_channel_upgrade = null
+ node_resource_group = "MC_rg-terraform_aks-terraform_westeurope"
+ oidc_issuer_enabled = false
+ open_service_mesh_enabled = null
+ private_cluster_enabled = false
+ private_cluster_public_fqdn_enabled = false
+ private_dns_zone_id = null
+ resource_group_name = "rg-terraform"
+ role_based_access_control_enabled = true
+ run_command_enabled = true
+ sku_tier = "Free"
+ tags = {}
+ workload_identity_enabled = false
+ default_node_pool {
+ capacity_reservation_group_id = null
+ custom_ca_trust_enabled = false
+ enable_auto_scaling = false
+ enable_host_encryption = false
+ enable_node_public_ip = false
+ fips_enabled = false
+ host_group_id = null
+ kubelet_disk_type = "OS"
+ max_count = 0
+ max_pods = 110
+ message_of_the_day = null
+ min_count = 0
+ name = "nodepool1"
+ node_count = 1
+ node_labels = {}
+ node_public_ip_prefix_id = null
+ node_taints = []
+ only_critical_addons_enabled = false
+ orchestrator_version = "1.26.6"
+ os_disk_size_gb = 128
+ os_disk_type = "Managed"
+ os_sku = "Ubuntu"
+ pod_subnet_id = null
+ proximity_placement_group_id = null
+ scale_down_mode = "Delete"
+ snapshot_id = null
+ tags = {}
+ temporary_name_for_rotation = null
+ type = "VirtualMachineScaleSets"
+ ultra_ssd_enabled = false
+ vm_size = "Standard_DS2_v2"
+ vnet_subnet_id = null
+ workload_runtime = "OCIContainer"
+ zones = []
+ }
+ identity {
+ identity_ids = []
+ type = "SystemAssigned"
+ }
+ kubelet_identity {
+ client_id = "52ae7539-062d-43a3-a4a0-4669ceab5ba0"
+ object_id = "d96aee84-1e72-4d77-92d5-c6bddfea0f3b"
+ user_assigned_identity_id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/MC_rg-terraform_aks-terraform_westeurope/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aks-terraform-agentpool"
+ }
+ linux_profile {
+ admin_username = "azureuser"
+ ssh_key {
+ key_data = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2SXD2alKvY6vixbgpxULdP/EIxTNccqvZTEO1sfcaswOPmUFxaO87JnJ4w+w446vQb/D847KJFxNql5OvLAFaoV1JT4YvrWw6D63wtZt/VwoTq5XDPnNFD8C6Ui4uwMCY2ZLcgg5suTCfe4lbyYWKkpFtFYK51eWrDA8UK3ndWFpwSgwR+FMDJ2qhCeMBSMSIVn6sAdPOvba1+gDQ1ELT8aLCXcdpnmY8yU/srj4b9qIGkZ01/s1OmJnUU3pdv41ygb4WB9rArGjWjl4+y9en6s4kPsUuUveij7+av7RqMwcy3Pb/61usoUILO9vahIe9T5PGsO7hPAKOkxEUIvEZ"
+ }
+ }
+ network_profile {
+ dns_service_ip = "10.0.0.10"
+ ebpf_data_plane = null
+ ip_versions = ["IPv4"]
+ load_balancer_sku = "standard"
+ network_mode = null
+ network_plugin = "kubenet"
+ network_plugin_mode = null
+ network_policy = null
+ outbound_type = "loadBalancer"
+ pod_cidr = "10.244.0.0/16"
+ pod_cidrs = ["10.244.0.0/16"]
+ service_cidr = "10.0.0.0/16"
+ service_cidrs = ["10.0.0.0/16"]
+ load_balancer_profile {
+ idle_timeout_in_minutes = 0
+ managed_outbound_ip_count = 1
+ managed_outbound_ipv6_count = 0
+ outbound_ip_address_ids = []
+ outbound_ip_prefix_ids = []
+ outbound_ports_allocated = 0
+ }
+ }
+}
+
+# __generated__ by Terraform
+resource "azurerm_key_vault_secret" "main" {
+ content_type = null
+ expiration_date = null
+ key_vault_id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.KeyVault/vaults/kv12357913tf"
+ name = "terraform-backend-key"
+ not_before_date = null
+ tags = {
+ file-encoding = "utf-8"
+ }
+ value = null # sensitive
+}
diff --git a/93_import_terraform/imports.tf b/93_import_terraform/imports.tf
new file mode 100644
index 0000000..0741481
--- /dev/null
+++ b/93_import_terraform/imports.tf
@@ -0,0 +1,49 @@
+import {
+ id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform"
+ to = azurerm_resource_group.main
+}
+
+import {
+ id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.KeyVault/vaults/kv12357913tf01"
+ to = azurerm_key_vault.main
+}
+
+import {
+ id = "https://kv12357913tf.vault.azure.net/secrets/terraform-backend-key/1ddace06fdbe4400a357474255564a2e "
+ to = azurerm_key_vault_secret.main
+}
+
+import {
+ id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.Compute/virtualMachines/vm-terraform"
+ to = azurerm_virtual_machine.main
+}
+
+import {
+ id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.Web/serverfarms/asp-terraform"
+ to = azurerm_app_service_plan.main
+}
+
+import {
+ id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.Web/sites/webapp-terraform"
+ to = azurerm_app_service.main
+}
+
+import {
+ id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.App/managedEnvironments/containerapp-terraform"
+ to = azurerm_container_app_environment.main
+}
+
+import {
+ id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.App/containerApps/containerapp-terraform"
+ to = azurerm_container_app.main
+}
+
+import {
+ id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.ContainerService/managedClusters/aks-terraform"
+ to = azurerm_kubernetes_cluster.main
+}
+
+import {
+ id = "/subscriptions/82f6d75e-85f4-434a-ab74-5dddd9fa8910/resourceGroups/rg-terraform/providers/Microsoft.ContainerService/managedClusters/aks-terraform/agentPools/npapps"
+ to = azurerm_kubernetes_cluster_node_pool.main
+}
diff --git a/93_import_terraform/providers.tf b/93_import_terraform/providers.tf
new file mode 100644
index 0000000..b6b6468
--- /dev/null
+++ b/93_import_terraform/providers.tf
@@ -0,0 +1,13 @@
+terraform {
+ required_providers {
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "3.75.0"
+ }
+ }
+}
+
+provider "azurerm" {
+ features {
+ }
+}
\ No newline at end of file