├── .github └── ISSUE_TEMPLATE │ └── feature_request.md ├── .gitignore ├── .vscode └── settings.json ├── AzureDevops-Introduction ├── README.md ├── aks-k8s │ ├── .gitignore │ ├── README.md │ ├── authentication.tf │ ├── kubernetes.tf │ ├── log_and_keyvault.tf │ ├── output.tf │ ├── secret │ │ └── README.md │ ├── var.tf │ └── variable │ │ ├── backend-jdld.tfvars │ │ └── main-jdld.tfvars └── azure-pipelines.yml ├── Best-Practice ├── Best-Practice.yml ├── BestPractice-1 │ ├── .gitignore │ ├── README.md │ ├── image │ │ ├── done.png │ │ ├── error.png │ │ └── tfstate.png │ ├── main.tf │ ├── secret │ │ └── README.md │ └── var.tf ├── BestPractice-2 │ ├── .gitignore │ ├── README.md │ ├── image │ │ ├── noversion.png │ │ └── version.png │ ├── main.tf │ ├── secret │ │ └── README.md │ ├── var.tf │ └── variable │ │ └── main-jdld.tfvars ├── BestPractice-3 │ ├── .gitignore │ ├── README.md │ ├── image │ │ ├── done.png │ │ ├── explicit.png │ │ └── implicit.png │ ├── main.tf │ ├── secret │ │ └── README.md │ ├── var.tf │ └── variable │ │ └── main-jdld.tfvars ├── BestPractice-4 │ ├── .gitignore │ ├── README.md │ ├── image │ │ ├── depl.png │ │ ├── destroy.png │ │ └── nodestroy.png │ ├── main.tf │ ├── secret │ │ └── README.md │ ├── var.tf │ └── variable │ │ └── main-jdld.tfvars └── README.md ├── CreateAzureRm-Infra ├── CreateAzureRm-Infra.yml ├── README.md ├── apps │ ├── README.md │ ├── main.tf │ ├── variable │ │ ├── apps-backend-jdld.tfvars │ │ └── apps-main-jdld.tfvars │ └── variables.tf ├── infra │ ├── README.md │ ├── application_gateway.tf │ ├── main.tf │ ├── variable │ │ ├── infra-backend-jdld.tfvars │ │ └── infra-main-jdld.tfvars │ └── variables.tf ├── secret │ └── README.md ├── tools │ ├── bash_functions.sh │ ├── handlebars.js │ ├── mustache.min.js │ ├── process_template.handlebar.js │ └── process_template.js └── workflow.png ├── LICENSE ├── README.md ├── _config.yml ├── _includes └── google-analytics.html ├── azurerm_application_gateway ├── README.md ├── main.tf └── provider.tf ├── module ├── Add-AzureRmLoadBalancerOutboundRules │ ├── AzureRmLoadBalancerOutboundRules_template.json │ ├── README.md │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf ├── Az-Bastion │ ├── AzureRmBastion_template.json │ ├── README.md │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf ├── Az-KeyVault │ ├── README.md │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf ├── Az-LoadBalancer │ └── README.md ├── Az-PolicyAssignment │ ├── README.md │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf ├── Az-PolicyDefinition │ ├── README.md │ ├── json_files │ │ ├── enforce-nsg-on-subnet_parameters.json │ │ ├── enforce-nsg-on-subnet_policy_rule.json │ │ ├── enforce-udr-on-subnet_parameters.json │ │ └── enforce-udr-on-subnet_policy_rule.json │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf ├── Az-RoleAssignment │ ├── README.md │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf ├── Az-RoleDefinition │ ├── README.md │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf ├── Az-VirtualNetwork │ └── README.md ├── Az-Vm │ └── README.md ├── Create-AzureRmPaloAltoAz │ ├── AzureRmPaloAltoAz_template.json │ ├── README.md │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf ├── Create-AzureRmPolicyDefinition │ └── json_files │ │ └── enforce-udr-on-subnet_parameters.json ├── Create-AzureRmRecoveryServicesVault │ ├── AzureRmRecoveryServicesVaultPolicy_template.json │ ├── AzureRmRecoveryServicesVault_template.json │ ├── README.md │ ├── main.tf │ ├── output.tf │ ├── sample_withoutarmtemplate.tf │ ├── var.tf │ └── versions.tf ├── Enable-AzureRmVirtualNetworkPeering │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf └── Get-AzureRmVirtualNetwork │ ├── README.md │ ├── main.tf │ ├── output.tf │ ├── var.tf │ └── versions.tf └── pipeline ├── PublishBuildArtifacts.yml └── terraform.yml /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: 5 | 6 | --- 7 | 8 | **Is your feature request related to a problem? Please describe.** 9 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | 11 | **Describe the solution you'd like** 12 | A clear and concise description of what you want to happen. 13 | 14 | **Describe alternatives you've considered** 15 | A clear and concise description of any alternative solutions or features you've considered. 16 | 17 | **Additional context** 18 | Add any other context or screenshots about the feature request here. 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | *.DS_Store 5 | *.tfstate.lock.info 6 | 7 | # Directory 8 | .terraform/ 9 | **/test 10 | create_sa/ 11 | 12 | # Secret files 13 | **/test.tf 14 | **/secret/*.json 15 | **/secret/*.tfvars -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.tf": "terraform", 4 | "*.tfvars": "terraform", 5 | "*.tfstate": "json" 6 | }, 7 | "editor.formatOnSave": true, 8 | "files.eol": "\n" 9 | } -------------------------------------------------------------------------------- /AzureDevops-Introduction/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | [![Build Status](https://dev.azure.com/jamesdld23/vpc_lab/_apis/build/status/JamesDLD.terraform%20Introduction?branchName=master)](https://dev.azure.com/jamesdld23/vpc_lab/_build/latest?definitionId=9&branchName=master) 4 | 5 | Content 6 | ------------ 7 | 8 | Share articles about CI/CD, Azure DevOps and Terraform on Azure. 9 | 10 | Reference 11 | ------------ 12 | 13 | | Link | Description | 14 | | ------------- | ------------- | 15 | | [Azure DevOps & Terraform - Concept](https://medium.com/@jamesdld23/a-cicd-journey-with-azure-devops-and-terraform-part-1-358f785b13f3) | CI/CD Concept | 16 | | [Azure DevOps & Terraform - Demo](https://medium.com/@jamesdld23/a-ci-cd-journey-with-azure-devops-and-terraform-part-2-524144511294) | Build your CI/CD pipeline for an Kubernetes cluster with Azure Kubernetes Service through Azure DevOps and Terraform 0.12 | 17 | -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | *.DS_Store 5 | 6 | # Directory 7 | .terraform/ 8 | 9 | # Secret files 10 | secret/*.tfvars 11 | -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | Create a Kubernetes cluster with Azure Kubernetes Service and Terraform 4 | ----- 5 | 6 | The Terraform code available here has been inspired from those Microsoft guides: 7 | 1. [Create a Kubernetes cluster with Azure Kubernetes Service and Terraform](https://docs.microsoft.com/en-us/azure/terraform/terraform-create-k8s-cluster-with-tf-and-aks) 8 | 2. [Quickstart: Deploy an Azure Kubernetes Service (AKS) cluster using the Azure CLI](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough) 9 | 10 | With your Terraform template created, the first step is to initialize Terraform. 11 | This step ensures that Terraform has all the prerequisites to build your template in Azure. 12 | 13 | ```hcl 14 | 15 | terraform init -backend-config="./variable/backend-jdld.tfvars" -backend-config="./secret/backend-jdld.json" -reconfigure 16 | 17 | ``` 18 | 19 | The terraform plan command is used to create an execution plan. 20 | This step compares the requested resources to the state information saved by Terraform and then gives as an output the planned execution. Resources are not created in Azure. 21 | ```hcl 22 | 23 | terraform plan -var-file="./variable/main-jdld.tfvars" -var-file="./secret/main-jdld.json" 24 | 25 | ``` 26 | 27 | If all is ok with the proposal you can now apply the configuration. 28 | ```hcl 29 | 30 | terraform apply -var-file="./variable/main-jdld.tfvars" -var-file="./secret/main-jdld.json" 31 | 32 | ``` 33 | 34 | To destroy the associated resources. 35 | ```hcl 36 | 37 | terraform destroy -var-file="./variable/main-jdld.tfvars" -var-file="./secret/main-jdld.json" 38 | 39 | ``` -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/authentication.tf: -------------------------------------------------------------------------------- 1 | #Set the terraform backend 2 | terraform { 3 | required_version = ">= 0.12.6" 4 | 5 | backend "azurerm" {} #Backend variables are initialized through the secret and variable folders 6 | } 7 | 8 | #Set the Provider 9 | provider "azurerm" { 10 | subscription_id = "${var.subscription_id}" 11 | client_id = "${var.client_id}" 12 | client_secret = "${var.client_secret}" 13 | tenant_id = "${var.tenant_id}" 14 | version = "~> 2.0" 15 | features {} 16 | } 17 | 18 | provider "azuread" { 19 | subscription_id = "${var.subscription_id}" 20 | client_id = "${var.client_id}" 21 | client_secret = "${var.client_secret}" 22 | tenant_id = "${var.tenant_id}" 23 | #version = "0.4.0" 24 | #Use the last version of the azurerm provider, reguraly tested by the following Azure DevOps pipeline : https://dev.azure.com/jamesdld23/vpc_lab/_build?definitionId=9&_a=summary 25 | } 26 | 27 | #Get the Resource Group and the SPN Id 28 | 29 | data "azurerm_resource_group" "Infr" { 30 | name = "${var.rg_infr_name}" 31 | } 32 | 33 | data "azuread_service_principal" "Infr" { 34 | application_id = "${var.client_id}" 35 | } 36 | -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/kubernetes.tf: -------------------------------------------------------------------------------- 1 | #Call module/resource 2 | resource "azurerm_kubernetes_cluster" "demo" { 3 | name = var.kubernetes_cluster["name"] 4 | location = data.azurerm_resource_group.Infr.location 5 | resource_group_name = data.azurerm_resource_group.Infr.name 6 | dns_prefix = var.kubernetes_cluster["dns_prefix"] 7 | 8 | agent_pool_profile { 9 | name = "default" 10 | count = 1 11 | vm_size = "Standard_D1_v2" 12 | os_type = "Linux" 13 | os_disk_size_gb = 30 14 | } 15 | 16 | service_principal { 17 | client_id = var.client_id 18 | client_secret = var.client_secret 19 | } 20 | 21 | addon_profile { 22 | oms_agent { 23 | enabled = true 24 | log_analytics_workspace_id = azurerm_log_analytics_workspace.demo.id 25 | } 26 | } 27 | 28 | tags = data.azurerm_resource_group.Infr.tags 29 | } 30 | 31 | -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/log_and_keyvault.tf: -------------------------------------------------------------------------------- 1 | #Call module/resource 2 | ### 3 | # Use Log Monitor with AKS 4 | ### 5 | resource "azurerm_log_analytics_workspace" "demo" { 6 | name = var.log_analytics_workspace["name"] 7 | location = data.azurerm_resource_group.Infr.location 8 | resource_group_name = data.azurerm_resource_group.Infr.name 9 | sku = var.log_analytics_workspace["sku"] 10 | tags = data.azurerm_resource_group.Infr.tags 11 | } 12 | 13 | resource "azurerm_log_analytics_solution" "demo" { 14 | count = length(var.log_analytics_workspace.solutions) 15 | solution_name = var.log_analytics_workspace.solutions[count.index]["name"] 16 | location = azurerm_log_analytics_workspace.demo.location 17 | resource_group_name = data.azurerm_resource_group.Infr.name 18 | workspace_resource_id = azurerm_log_analytics_workspace.demo.id 19 | workspace_name = azurerm_log_analytics_workspace.demo.name 20 | 21 | plan { 22 | publisher = var.log_analytics_workspace.solutions[count.index]["publisher"] 23 | product = var.log_analytics_workspace.solutions[count.index]["product"] 24 | } 25 | } 26 | 27 | ### 28 | # Store the Kubernetes AKS cluster credential in the Key Vault 29 | ### 30 | data "azurerm_client_config" "current" {} 31 | 32 | resource "azurerm_key_vault" "demo" { 33 | name = var.key_vault["name"] 34 | location = data.azurerm_resource_group.Infr.location 35 | resource_group_name = data.azurerm_resource_group.Infr.name 36 | tenant_id = data.azurerm_client_config.current.tenant_id 37 | sku_name = var.key_vault["sku"] 38 | 39 | access_policy { 40 | tenant_id = data.azurerm_client_config.current.tenant_id 41 | object_id = data.azurerm_client_config.current.service_principal_object_id 42 | 43 | certificate_permissions = [ 44 | "create", 45 | "delete", 46 | "deleteissuers", 47 | "get", 48 | "getissuers", 49 | "import", 50 | "list", 51 | "listissuers", 52 | "managecontacts", 53 | "manageissuers", 54 | "setissuers", 55 | "update", 56 | ] 57 | 58 | key_permissions = [ 59 | "backup", 60 | "create", 61 | "decrypt", 62 | "delete", 63 | "encrypt", 64 | "get", 65 | "import", 66 | "list", 67 | "purge", 68 | "recover", 69 | "restore", 70 | "sign", 71 | "unwrapKey", 72 | "update", 73 | "verify", 74 | "wrapKey", 75 | ] 76 | 77 | secret_permissions = [ 78 | "backup", 79 | "delete", 80 | "get", 81 | "list", 82 | "purge", 83 | "recover", 84 | "restore", 85 | "set", 86 | ] 87 | } 88 | 89 | tags = data.azurerm_resource_group.Infr.tags 90 | } 91 | 92 | resource "azurerm_key_vault_secret" "demo" { 93 | name = "providerkubernetes" 94 | value = azurerm_kubernetes_cluster.demo.kube_config.0.password 95 | key_vault_id = azurerm_key_vault.demo.id 96 | 97 | tags = { 98 | host = "${azurerm_kubernetes_cluster.demo.kube_config.0.host}" 99 | username = "${azurerm_kubernetes_cluster.demo.kube_config.0.username}" 100 | #client_certificate = "${base64decode(azurerm_kubernetes_cluster.demo.kube_config.0.client_certificate)}" 101 | #client_key = "${base64decode(azurerm_kubernetes_cluster.demo.kube_config.0.client_key)}" 102 | #cluster_ca_certificate = "${base64decode(azurerm_kubernetes_cluster.demo.kube_config.0.cluster_ca_certificate)}" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/output.tf: -------------------------------------------------------------------------------- 1 | #Terraform outputs allow you to define values that will be highlighted to the user when Terraform applies a plan, and can be queried using the terraform output command. 2 | output "provider_kubernetes" { 3 | value = { 4 | host = azurerm_kubernetes_cluster.demo.kube_config.0.host 5 | username = azurerm_kubernetes_cluster.demo.kube_config.0.username 6 | password = azurerm_kubernetes_cluster.demo.kube_config.0.password 7 | client_certificate = base64decode(azurerm_kubernetes_cluster.demo.kube_config.0.client_certificate) 8 | client_key = base64decode(azurerm_kubernetes_cluster.demo.kube_config.0.client_key) 9 | cluster_ca_certificate = base64decode(azurerm_kubernetes_cluster.demo.kube_config.0.cluster_ca_certificate) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/secret/README.md: -------------------------------------------------------------------------------- 1 | Folder's golden rule 2 | ------------ 3 | - Store secrets 4 | - File containing password are ignored by any git push 5 | 6 | 7 | Secret files 8 | ------------ 9 | - backend-jdld.json : stores the credential to write Terraform backend files 10 | - Content sample ==> 11 | ```json 12 | { 13 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 14 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 15 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 16 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 17 | } 18 | ``` 19 | - main-jdld.json : stores the credential to write in Infra and Apps Resource groups and store the VM credentials 20 | - Content sample ==> 21 | ```json 22 | { 23 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 24 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 25 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 26 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 27 | } 28 | ``` -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/var.tf: -------------------------------------------------------------------------------- 1 | #Variables declaration 2 | 3 | variable "subscription_id" { 4 | description = "Azure subscription Id." 5 | } 6 | 7 | variable "tenant_id" { 8 | description = "Azure tenant Id." 9 | } 10 | 11 | variable "client_id" { 12 | description = "Azure service principal application Id" 13 | } 14 | 15 | variable "client_secret" { 16 | description = "Azure service principal application Secret" 17 | } 18 | 19 | variable "rg_infr_name" { 20 | description = "Resource group name that will host our services" 21 | } 22 | 23 | variable log_analytics_workspace { 24 | description = "The log analytics workspace" 25 | type = any 26 | /* Following code is currently raising an error 27 | map(object({ 28 | 29 | name=string #The log analytics workspace name must be unique 30 | sku=string #Refer https://azure.microsoft.com/pricing/details/monitor/ for log analytics pricing 31 | 32 | solutions = list(object({ 33 | name = string 34 | publisher = string 35 | product = string 36 | })) 37 | }))*/ 38 | } 39 | 40 | variable "key_vault" { 41 | description = "The key vault" 42 | type = map(string) 43 | # type = map(object({ 44 | # name = string #The key vault name must be unique 45 | # sku = string 46 | # })) 47 | } 48 | 49 | variable "kubernetes_cluster" { 50 | description = "Kubernetes cluster with Azure Kubernetes Service" 51 | type = any 52 | } 53 | -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/variable/backend-jdld.tfvars: -------------------------------------------------------------------------------- 1 | #Variables initialization 2 | resource_group_name = "infr-jdld-noprd-rg1" 3 | 4 | storage_account_name = "infrasdbx1vpcjdld1" #Name must be unique 5 | 6 | container_name = "tfstate" 7 | 8 | key = "aks-k8s.tfstate" 9 | -------------------------------------------------------------------------------- /AzureDevops-Introduction/aks-k8s/variable/main-jdld.tfvars: -------------------------------------------------------------------------------- 1 | #Variables initialization 2 | 3 | ### 4 | # Core 5 | ### 6 | 7 | rg_infr_name = "infr-jdld-noprd-rg1" 8 | 9 | ### 10 | # Log & Certificate 11 | ### 12 | 13 | log_analytics_workspace = { 14 | name = "demojdldsdbxlogmon1" #The log analytics workspace name must be unique 15 | sku = "PerGB2018" #Refer to https://azure.microsoft.com/pricing/details/monitor/ for log analytics pricing 16 | 17 | solutions = [{ 18 | name = "ContainerInsights" 19 | publisher = "Microsoft" 20 | product = "OMSGallery/ContainerInsights" 21 | }] 22 | } 23 | 24 | key_vault = { 25 | name = "demojdldsdbxkeyv2" #The kay vault name must be unique 26 | sku = "standard" 27 | } 28 | 29 | ### 30 | # AKS 31 | ### 32 | kubernetes_cluster = { 33 | name = "aks-demo-jdld" 34 | dns_prefix = "aksdemojdld" #Please see https://aka.ms/aks-naming-rules 35 | } 36 | -------------------------------------------------------------------------------- /AzureDevops-Introduction/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | #Multi-stage YAML pipeline demo. 2 | name: $(BuildDefinitionName).$(DayOfYear)$(Rev:.r) 3 | 4 | schedules: 5 | - cron: "45 6 * * 4" 6 | branches: 7 | include: 8 | - master 9 | displayName: Weekly Thursday 6h45 am UTC build 10 | always: true 11 | 12 | variables: 13 | - name: terraform_version 14 | value: "0.12.13" 15 | - name: vmImageName 16 | value: "ubuntu-latest" 17 | - name: backend_main_secret_file_id1 # secret id located in your Azure DevOps library, file used by the following cmdlet Terraform init, plan, apply and destroy 18 | value: "backend-main-jdld-1.json" 19 | - name: artifact_name 20 | value: "AzureDevops-Introduction" 21 | 22 | resources: 23 | repositories: 24 | - repository: Yaml_Templates # identifier (A-Z, a-z, 0-9, and underscore) 25 | type: git #git refers to Azure Repos Git repos 26 | name: Template/template_pipeline #To refer to a repo in another project within the same organization, prefix the name with that project's name. For example, OtherProject/otherRepo. 27 | ref: refs/tags/0.1.0 # ref name to use, defaults to 'refs/heads/master' 28 | 29 | trigger: 30 | batch: true # when a build is running, the system waits until the build is completed 31 | branches: 32 | include: 33 | - master 34 | - feature/* 35 | - release/* 36 | paths: 37 | include: 38 | - AzureDevops-Introduction/* 39 | 40 | stages: 41 | - stage: Build 42 | jobs: 43 | - job: Terraform_Plan 44 | displayName: Terraform Plan - Publish a package if Infrastructure changes are identified 45 | continueOnError: false 46 | pool: 47 | vmImage: $(vmImageName) 48 | steps: 49 | - task: DownloadSecureFile@1 50 | displayName: "Download secure file $(backend_main_secret_file_id1)" 51 | inputs: 52 | secureFile: $(backend_main_secret_file_id1) 53 | 54 | - template: terraform.yml@Yaml_Templates 55 | parameters: 56 | version: $(terraform_version) 57 | path: "./AzureDevops-Introduction/aks-k8s/" 58 | package_name: "aks-k8s" 59 | terraform_init: true 60 | terraform_plan: true 61 | backend_secret_file_id: $(backend_main_secret_file_id1) 62 | backend_file_path: "variable/backend-jdld.tfvars" 63 | main_secret_file_id: $(backend_main_secret_file_id1) 64 | main_file_path: "variable/main-jdld.tfvars" 65 | 66 | - publish: "./ArtifactPublishLocation" # Local path to include in the Artifact 67 | artifact: "$(artifact_name)" 68 | 69 | - stage: Deploy 70 | dependsOn: Build 71 | jobs: 72 | # track deployments on the environment 73 | - deployment: Terraform_Apply 74 | displayName: Terraform Apply - Resources creation 75 | pool: 76 | vmImage: $(vmImageName) 77 | environment: "Terraform_Apply" 78 | strategy: 79 | # default deployment strategy 80 | runOnce: 81 | deploy: 82 | steps: 83 | - template: terraform.yml@Yaml_Templates 84 | parameters: 85 | version: $(terraform_version) 86 | artifact_path: $(Pipeline.Workspace)/$(artifact_name) 87 | package_name: "aks-k8s" 88 | terraform_apply: true 89 | main_file_path: "variable/main-jdld.tfvars" 90 | 91 | - stage: Deliver 92 | dependsOn: Deploy 93 | jobs: 94 | # track deployments on the environment 95 | - deployment: Terraform_Destroy 96 | displayName: Terraform Destroy - Script ok, now deleting the resources 97 | pool: 98 | vmImage: $(vmImageName) 99 | environment: "Terraform_Destroy" 100 | strategy: 101 | # default deployment strategy 102 | runOnce: 103 | deploy: 104 | steps: 105 | - task: DownloadSecureFile@1 106 | displayName: Download secure file $(backend_main_secret_file_id1) 107 | inputs: 108 | secureFile: $(backend_main_secret_file_id1) 109 | 110 | - template: terraform.yml@Yaml_Templates 111 | parameters: 112 | version: $(terraform_version) 113 | artifact_path: $(Pipeline.Workspace)/$(artifact_name) 114 | package_name: "aks-k8s" 115 | terraform_destroy: true 116 | main_secret_file_id: $(backend_main_secret_file_id1) 117 | main_file_path: "variable/main-jdld.tfvars" 118 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-1/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | *.DS_Store 5 | 6 | # Directory 7 | .terraform/ 8 | 9 | # Secret files 10 | secret/*.tfvars 11 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-1/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | Best Practice 1 4 | ------------ 5 | Use remote backend. 6 | In this article we will perform the following action with and without a remote backend : 7 | 1. Create a Virtual Network 8 | 2. Assign a Role Definition to the Virtual Network 9 | 10 | Through the second action we will be able to demonstrate one of the reason why we should use a remote backend : some resources like "azurerm_role_assignment" couldn't update their tfstate if the "role assignment" is already done. [This article on GitHub](https://github.com/terraform-providers/terraform-provider-azurerm/issues/1857) describes this topic. 11 | 12 | In other words ... if you don't have the tfstate, your Terraform `apply` could fail and the only way to re adjust it would be : 13 | 1. to find this tfstate back (would have been good to have it on a remote location) or 14 | 2. to perform Terraform import on the the resources that you can't integrate back in your tfstate. 15 | 16 | 17 | ### Prerequisite 18 | ----- 19 | 20 | | Item | Description | 21 | | ------------- | ------------- | 22 | | Azure Subscription | An Azure subscription id | 23 | | Resource Group | An Azure resource group is available | 24 | | Storage Account | An Azure storage account is available and is located in the upper resource group, it contains a container named `tfstate` | 25 | | Service Principal | An Azure service principal is available and has the `owner` privilege on the upper resource group | 26 | | Terraform file | [Clone this repository](https://github.com/JamesDLD/terraform/tree/master/Best-Practice/BestPractice-1) and fill in the following files with the upper prerequisite items :
Variable used for the Terraform `init` : secret/backend-jdld.json
Variable used for the Terraform `plan` and `apply` : [main.tf](main.tf) & secret/main-jdld.json | 27 | 28 | 29 | What should we do? 30 | ------------ 31 | We will create the upper mentioned element using remote backend. 32 | 33 | In the following article our remote backend will be located on an Azure Storage Account and we will use a service principal to write on this storage account. 34 | To do so we will have to declare the following bracket in our Terraform tf file. 35 | ```hcl 36 | terraform { 37 | backend "azurerm" { 38 | storage_account_name = "infrasdbx1vpcjdld1" 39 | container_name = "tfstate" 40 | key = "BestPractice-1.tfstate" 41 | resource_group_name = "infr-jdld-noprd-rg1" 42 | subscription_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 43 | client_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 44 | client_secret = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 45 | tenant_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 46 | } 47 | } 48 | ``` 49 | 50 | 51 | 52 | ### 1. Usage 53 | ----- 54 | 55 | This step ensures that Terraform has all the prerequisites to build your template in Azure. 56 | ```hcl 57 | terraform init -backend-config="secret/backend-jdld.json" -reconfigure 58 | ``` 59 | 60 | The terraform plan command is used to create an execution plan. 61 | This step compares the requested resources to the state information saved by Terraform and then gives as an output the planned execution. Resources are not created in Azure. 62 | ```hcl 63 | terraform plan -var-file="secret/main-jdld.json" 64 | ``` 65 | 66 | If all is ok with the proposal you can now apply the configuration. 67 | ```hcl 68 | terraform apply -var-file="secret/main-jdld.json" 69 | ``` 70 | 71 | ### 2. Analysis 72 | ----- 73 | 74 | | Description | Screenshot | 75 | | ------------- | ------------- | 76 | | Our objects have been created on Azure | ![done](image/done.png) | 77 | | Our remote backend has generated a Terraform tfstate with all our objects specifications | ![tfstate](image/tfstate.png) | 78 | 79 | 80 | What shouldn't we do? 81 | ------------ 82 | We will now forget the backend specification. 83 | This will imply that we will no longer depend on a remote backend, in other words we will not be aware of any other deployment of someone else. 84 | 85 | In here we will demonstrate that remote backend encourage collaboration. 86 | 87 | Let's remove the following bracket : backend "azurerm" {}. 88 | The top part of our [main.tf](main.tf) script will look like the following : 89 | ```hcl 90 | terraform { 91 | } 92 | ``` 93 | 94 | ### 1. Usage 95 | ----- 96 | 97 | This step ensures that Terraform has all the prerequisites to build your template in Azure. 98 | ```hcl 99 | terraform init -backend-config="secret/backend-jdld.json" -reconfigure 100 | ``` 101 | > When the message `Do you want to copy existing state to the new backend?` appears enter `no` and press enter. 102 | 103 | You can now apply the configuration. 104 | ```hcl 105 | terraform apply -var-file="secret/main-jdld.json" 106 | ``` 107 | 108 | ### 2. Analysis 109 | ----- 110 | 111 | | Description | Screenshot | 112 | | ------------- | ------------- | 113 | | Our objects are still on Azure | ![done](image/done.png) | 114 | | The Terraform fails because for some resource like "azurerm_role_assignment" we can’t automatically
update our tfstate telling that the resource is already created as we wished.
Note that it wasn’t
the case for the resource "azurerm_virtual_network" | ![tfstate](image/error.png) | 115 | 116 | 117 | 118 | See you! 119 | 120 | JamesDLD -------------------------------------------------------------------------------- /Best-Practice/BestPractice-1/image/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-1/image/done.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-1/image/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-1/image/error.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-1/image/tfstate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-1/image/tfstate.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-1/main.tf: -------------------------------------------------------------------------------- 1 | #Set the terraform backend 2 | terraform { 3 | backend "azurerm" { 4 | storage_account_name = "infrasdbx1vpcjdld1" 5 | container_name = "tfstate" 6 | key = "BestPractice-1.tfstate" 7 | resource_group_name = "infr-jdld-noprd-rg1" 8 | } 9 | } 10 | 11 | #Set the Provider 12 | provider "azurerm" { 13 | subscription_id = var.subscription_id 14 | client_id = var.client_id 15 | client_secret = var.client_secret 16 | tenant_id = var.tenant_id 17 | version = "~> 2.0" 18 | features {} 19 | } 20 | 21 | #Variables 22 | variable "subnets" { 23 | description = "Subnets list." 24 | 25 | type = list(object({ 26 | name = string 27 | address_prefix = string 28 | })) 29 | 30 | default = [ 31 | { 32 | name = "bp1-front-snet1" 33 | address_prefix = "10.0.1.0/24" 34 | }, 35 | { 36 | name = "bp1-front-snet2" 37 | address_prefix = "10.0.6.0/24" 38 | } 39 | ] 40 | } 41 | 42 | 43 | #Call module/resource 44 | resource "azurerm_virtual_network" "bp1-vnet1" { 45 | name = "bp1-vnet1" 46 | location = "westeurope" 47 | resource_group_name = "infr-jdld-noprd-rg1" 48 | address_space = ["10.0.0.0/16"] 49 | 50 | dynamic "subnet" { 51 | for_each = var.subnets 52 | 53 | content { 54 | name = lookup(subnet.value, "name", null) 55 | address_prefix = lookup(subnet.value, "address_prefix", null) 56 | } 57 | } 58 | 59 | tags = { 60 | environment = "Test" 61 | } 62 | } 63 | 64 | data "azurerm_subscription" "primary" { 65 | } 66 | 67 | data "azurerm_client_config" "test" { 68 | } 69 | 70 | resource "azurerm_role_assignment" "test" { 71 | scope = azurerm_virtual_network.bp1-vnet1.id 72 | role_definition_name = "Reader" 73 | principal_id = data.azurerm_client_config.test.service_principal_object_id 74 | } 75 | 76 | output "subnet_ids" { 77 | description = "The subnet Ids." 78 | value = azurerm_virtual_network.bp1-vnet1.subnet.*.id 79 | } 80 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-1/secret/README.md: -------------------------------------------------------------------------------- 1 | Folder's golden rule 2 | ------------ 3 | - Store secrets 4 | - File containing password are ignored by any git push 5 | 6 | 7 | Secret files 8 | ------------ 9 | - backend-jdld.json : stores the credential to write Terraform backend files 10 | - Content sample ==> 11 | ```json 12 | { 13 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 14 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 15 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 16 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 17 | } 18 | ``` 19 | - main-jdld.json : stores the credential to write in Infra and Apps Resource groups and store the VM credentials 20 | - Content sample ==> 21 | ```json 22 | { 23 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 24 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 25 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 26 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 27 | } 28 | ``` -------------------------------------------------------------------------------- /Best-Practice/BestPractice-1/var.tf: -------------------------------------------------------------------------------- 1 | #Variables declaration 2 | 3 | variable "subscription_id" { 4 | description = "Azure subscription Id." 5 | } 6 | 7 | variable "tenant_id" { 8 | description = "Azure tenant Id." 9 | } 10 | 11 | variable "client_id" { 12 | description = "Azure service principal application Id" 13 | } 14 | 15 | variable "client_secret" { 16 | description = "Azure service principal application Secret" 17 | } 18 | 19 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-2/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | *.DS_Store 5 | 6 | # Directory 7 | .terraform/ 8 | 9 | # Secret files 10 | secret/*.tfvars 11 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-2/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | Best Practice 2 4 | ------------ 5 | In this article we will see how to set Terraform, provider and modules version (see [this article](https://www.terraform.io/docs/configuration/terraform.html) from terraform.io website to learn more about managing Terraform version). 6 | 7 | In this article we will perform the following action : 8 | 1. Get a Resource Group 9 | 2. Create a Virtual Network and a Subnet in this Virtual Network 10 | 11 | 12 | ### Prerequisite 13 | ----- 14 | 15 | | Item | Description | 16 | | ------------- | ------------- | 17 | | Azure Subscription | An Azure subscription id | 18 | | Resource Group | An Azure resource group is available | 19 | | Storage Account | An Azure storage account is available and is located in the upper resource group, it contains a container named `tfstate` | 20 | | Service Principal | An Azure service principal is available and has the `owner` privilege on the upper resource group | 21 | | Terraform file | [Clone this repository](https://github.com/JamesDLD/terraform/tree/master/Best-Practice/BestPractice-2) and fill in the following files with the upper prerequisite items :
Variable used for the Terraform `init` : secret/backend-jdld.json
Variable used for the Terraform `plan` and `apply` : [main.tf](main.tf) & [main-jdld.tfvars](variable/main-jdld.tfvars) & secret/main-jdld.json | 22 | 23 | 24 | 25 | What should we do? 26 | ------------ 27 | We will create the upper mentioned element using remote backend (see the previous article [BestPractice-1](../BestPractice-1) for more information about remote backend). 28 | 29 | The Terraform executable file, the AzureRm provider and our modules version will be set as described in the following bracket (also available in our [main-jdld.tf](main-jdld.tf) Terraform file). 30 | 31 | 32 | Declare the Terraform required version 33 | ```hcl 34 | terraform { 35 | required_version = ">= 0.12.6" 36 | 37 | backend "azurerm" { 38 | storage_account_name = "infrasdbx1vpcjdld1" 39 | container_name = "tfstate" 40 | key = "BestPractice-2.tfstate" 41 | resource_group_name = "infr-jdld-noprd-rg1" 42 | } 43 | } 44 | ``` 45 | 46 | Specify the AzureRm version 47 | ```hcl 48 | provider "azurerm" { 49 | version = ">= 1.31.0" 50 | subscription_id = "${var.subscription_id}" 51 | client_id = "${var.client_id}" 52 | client_secret = "${var.client_secret}" 53 | tenant_id = "${var.tenant_id}" 54 | version = "~> 2.0" 55 | features {} 56 | } 57 | ``` 58 | 59 | Specify the module version 60 | ```hcl 61 | module "Az-VirtualNetwork" { 62 | source = "JamesDLD/Az-VirtualNetwork/azurerm" 63 | version = "0.1.4" 64 | net_prefix = "demo" 65 | network_resource_group_name = data.azurerm_resource_group.bp2.name 66 | virtual_networks = var.virtual_networks 67 | subnets = var.subnets 68 | route_tables = {} 69 | network_security_groups = {} 70 | } 71 | ``` 72 | 73 | 74 | 75 | ### 1. Usage 76 | ----- 77 | 78 | This step ensures that Terraform has all the prerequisites to build your template in Azure. 79 | ```hcl 80 | terraform init -backend-config="secret/backend-jdld.json" -reconfigure 81 | ``` 82 | 83 | The terraform plan command is used to create an execution plan. 84 | This step compares the requested resources to the state information saved by Terraform and then gives as an output the planned execution. Resources are not created in Azure. 85 | ```hcl 86 | terraform plan -var-file="secret/main-jdld.json" -var-file="variable/main-jdld.tfvars" 87 | ``` 88 | 89 | If all is ok with the proposal you can now apply the configuration. 90 | ```hcl 91 | terraform apply -var-file="secret/main-jdld.json" -var-file="variable/main-jdld.tfvars" 92 | ``` 93 | 94 | ### 2. Analysis 95 | ----- 96 | 97 | | Description | Screenshot | 98 | | ------------- | ------------- | 99 | | The Terraform `init` highlights our version | ![version](image/version.png) | 100 | | Check the Terraform init when you remove the version | ![noversion](image/noversion.png) | 101 | 102 | 103 | See you! 104 | 105 | JamesDLD -------------------------------------------------------------------------------- /Best-Practice/BestPractice-2/image/noversion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-2/image/noversion.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-2/image/version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-2/image/version.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-2/main.tf: -------------------------------------------------------------------------------- 1 | #Set the terraform backend 2 | terraform { 3 | required_version = ">= 0.12.6" 4 | 5 | backend "azurerm" { 6 | storage_account_name = "infrasdbx1vpcjdld1" 7 | container_name = "tfstate" 8 | key = "BestPractice-2.tfstate" 9 | resource_group_name = "infr-jdld-noprd-rg1" 10 | } 11 | } 12 | 13 | #Set the Provider 14 | provider "azurerm" { 15 | version = ">= 1.31.0" #Use the last version tested through an Azure DevOps pipeline here : https://dev.azure.com/jamesdld23/vpc_lab/_build 16 | subscription_id = var.subscription_id 17 | client_id = var.client_id 18 | client_secret = var.client_secret 19 | tenant_id = var.tenant_id 20 | version = "~> 2.0" 21 | features {} 22 | } 23 | 24 | #Call module/resource 25 | #Get components 26 | data "azurerm_resource_group" "bp2" { 27 | name = "infr-jdld-noprd-rg1" 28 | } 29 | 30 | #Action 31 | module "Az-VirtualNetwork-demo" { 32 | source = "JamesDLD/Az-VirtualNetwork/azurerm" 33 | version = "0.1.4" 34 | net_prefix = "demo" 35 | network_resource_group_name = data.azurerm_resource_group.bp2.name 36 | virtual_networks = { 37 | vnet1 = { 38 | id = "1" 39 | prefix = "bp2" 40 | address_space = ["198.18.1.0/24", "198.18.2.0/24"] 41 | } 42 | } 43 | subnets = var.subnets 44 | route_tables = {} 45 | network_security_groups = {} 46 | } 47 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-2/secret/README.md: -------------------------------------------------------------------------------- 1 | Folder's golden rule 2 | ------------ 3 | - Store secrets 4 | - File containing password are ignored by any git push 5 | 6 | 7 | Secret files 8 | ------------ 9 | - backend-jdld.json : stores the credential to write Terraform backend files 10 | - Content sample ==> 11 | ```json 12 | { 13 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 14 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 15 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 16 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 17 | } 18 | ``` 19 | - main-jdld.json : stores the credential to write in Infra and Apps Resource groups and store the VM credentials 20 | - Content sample ==> 21 | ```json 22 | { 23 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 24 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 25 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 26 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 27 | } 28 | ``` -------------------------------------------------------------------------------- /Best-Practice/BestPractice-2/var.tf: -------------------------------------------------------------------------------- 1 | #Variables declaration 2 | 3 | variable "subscription_id" { 4 | description = "Azure subscription Id." 5 | } 6 | 7 | variable "tenant_id" { 8 | description = "Azure tenant Id." 9 | } 10 | 11 | variable "client_id" { 12 | description = "Azure service principal application Id" 13 | } 14 | 15 | variable "client_secret" { 16 | description = "Azure service principal application Secret" 17 | } 18 | 19 | variable "subnets" { 20 | description = "Subnet list." 21 | type = any 22 | } 23 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-2/variable/main-jdld.tfvars: -------------------------------------------------------------------------------- 1 | #Variables initialization 2 | 3 | subnets = { 4 | snet1 = { 5 | vnet_key = "vnet1" #(Mandatory) 6 | name = "bp2" #(Mandatory) 7 | address_prefix = "198.18.1.0/26" #(Mandatory) 8 | service_endpoints = ["Microsoft.Sql", "Microsoft.Storage"] #(Optional) delete this line for no Service Endpoints 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-3/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | *.DS_Store 5 | 6 | # Directory 7 | .terraform/ 8 | 9 | # Secret files 10 | secret/*.tfvars 11 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-3/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | Best Practice 3 4 | ------------ 5 | Implicit dependencies should be used whenever possible (see [this article](https://www.terraform.io/intro/getting-started/dependencies.html) from terraform.io website for more information). 6 | In this article we will perform the following action with *implicit* dependencies and with *explicit* dependencies : 7 | 1. Create 1 load balancer 8 | 2. Create 2 network interfaces and link them to a load balancer 9 | 10 | 11 | ### Prerequisite 12 | ----- 13 | 14 | | Item | Description | 15 | | ------------- | ------------- | 16 | | Azure Subscription | An Azure subscription id | 17 | | Resource Group | An Azure resource group is available | 18 | | Storage Account | An Azure storage account is available and is located in the upper resource group, it contains a container named `tfstate` | 19 | | Service Principal | An Azure service principal is available and has the `owner` privilege on the upper resource group | 20 | | Terraform file | [Clone this repository](https://github.com/JamesDLD/terraform/tree/master/Best-Practice/BestPractice-3) and fill in the following files with the upper prerequisite items :
Variable used for the Terraform `init` : secret/backend-jdld.json
Variable used for the Terraform `plan` and `apply` : [main.tf](main.tf) & [main-jdld.tfvars](variable/main-jdld.tfvars) & secret/main-jdld.json | 21 | 22 | 23 | 24 | What should we do? 25 | ------------ 26 | We will create the upper mentioned element using remote backend (see the previous article [BestPractice-1](../BestPractice-1) for more information about remote backend). 27 | 28 | Review the code [main.tf](main.tf), as illustrated in the following bracket, the *implicit* and the *explicit* methods are highlighted. 29 | ```hcl 30 | module "Az-Vm-Demo" { 31 | source = "JamesDLD/Az-Vm/azurerm" 32 | version = "0.1.2" 33 | sa_bootdiag_storage_uri = data.azurerm_storage_account.bp3.primary_blob_endpoint #(Mandatory) 34 | subnets_ids = module.Az-VirtualNetwork-Demo.subnet_ids #(Mandatory) 35 | linux_vms = {} #(Mandatory) 36 | windows_vms = var.vms #(Mandatory) 37 | 38 | #This an implicit dependency 39 | internal_lb_backend_ids = module.Create-AzureRmLoadBalancer-Demo.lb_backend_ids 40 | 41 | #This an explicit dependency 42 | #internal_lb_backend_ids = ["/subscriptions/${var.subscription_id}/resourceGroups/infr-jdld-noprd-rg1/providers/Microsoft.Network/loadBalancers/demo-bp3-internal-lb1/backendAddressPools/demo-bp3-internal-bckpool1"] 43 | 44 | vm_resource_group_name = data.azurerm_resource_group.bp3.name 45 | vm_prefix = "bp3" #(Optional) 46 | windows_storage_image_reference = var.windows_storage_image_reference #(Optional) 47 | admin_username = var.app_admin 48 | admin_password = var.pass 49 | vm_additional_tags = { bp = "3" } #(Optional) 50 | } 51 | ``` 52 | 53 | 54 | 55 | ### 1. Usage 56 | ----- 57 | 58 | This step ensures that Terraform has all the prerequisites to build your template in Azure. 59 | ```hcl 60 | terraform init -backend-config="secret/backend-jdld.json" -reconfigure 61 | ``` 62 | 63 | The Terraform plan command is used to create an execution plan. 64 | This step compares the requested resources to the state information saved by Terraform and then gives as an output the planned execution. Resources are not created in Azure. 65 | ```hcl 66 | terraform plan -var-file="secret/main-jdld.json" -var-file="variable/main-jdld.tfvars" 67 | ``` 68 | 69 | If all is ok with the proposal you can now apply the configuration with both methods (implicit and explicit) to track the impact. 70 | ```hcl 71 | terraform apply -var-file="secret/main-jdld.json" -var-file="variable/main-jdld.tfvars" 72 | ``` 73 | 74 | We will now destroy what we have done with both methods (implicit and explicit) to track the impact. 75 | ```hcl 76 | terraform destroy -var-file="secret/main-jdld.json" -var-file="variable/main-jdld.tfvars" 77 | ``` 78 | 79 | ### 2. Analysis 80 | ----- 81 | 82 | | Description | Screenshot | 83 | | ------------- | ------------- | 84 | | Our 2 network interfaces are linked to the Load Balancer | ![done](image/done.png) | 85 | | When using implicit dependencies all is working like a charm | ![implicit](image/implicit.png) | 86 | | When using explicit dependencies we receive error(s) because some resources doesn't wait for
other to be created (a workaround consists in using the variable `depend on` but this will still
cause error when you will proceed Terraform `destroy`) | ![explicit](image/explicit.png) | 87 | 88 | 89 | See you! 90 | 91 | JamesDLD 92 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-3/image/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-3/image/done.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-3/image/explicit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-3/image/explicit.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-3/image/implicit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-3/image/implicit.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-3/main.tf: -------------------------------------------------------------------------------- 1 | #Set the terraform backend 2 | terraform { 3 | required_version = ">= 0.12.6" 4 | 5 | backend "azurerm" { 6 | storage_account_name = "infrasdbx1vpcjdld1" 7 | container_name = "tfstate" 8 | key = "BestPractice-3.tfstate" 9 | resource_group_name = "infr-jdld-noprd-rg1" 10 | } 11 | } 12 | 13 | #Set the Provider 14 | provider "azurerm" { 15 | version = "~> 2.0" 16 | subscription_id = var.subscription_id 17 | client_id = var.client_id 18 | client_secret = var.client_secret 19 | tenant_id = var.tenant_id 20 | features {} 21 | } 22 | 23 | #Call module/resource 24 | #Get components 25 | 26 | data "azurerm_resource_group" "bp3" { 27 | name = "infr-jdld-noprd-rg1" 28 | } 29 | 30 | data "azurerm_storage_account" "bp3" { 31 | name = "infrasdbx1vpcjdld1" 32 | resource_group_name = data.azurerm_resource_group.bp3.name 33 | } 34 | 35 | #Action 36 | module "Az-VirtualNetwork-Demo" { 37 | source = "JamesDLD/Az-VirtualNetwork/azurerm" 38 | version = "0.1.4" 39 | net_prefix = "demo" 40 | network_resource_group_name = data.azurerm_resource_group.bp3.name 41 | virtual_networks = { 42 | vnet1 = { 43 | id = "1" 44 | prefix = "bp3" 45 | address_space = ["10.0.3.0/24"] 46 | } 47 | } 48 | subnets = var.subnets 49 | route_tables = {} 50 | network_security_groups = {} 51 | } 52 | 53 | module "Create-AzureRmLoadBalancer-Demo" { 54 | source = "JamesDLD/Az-LoadBalancer/azurerm" 55 | version = "0.1.1" 56 | Lbs = var.Lbs 57 | LbRules = {} 58 | lb_prefix = "demo-bp3" 59 | lb_location = element(module.Az-VirtualNetwork-Demo.vnet_locations, 0) 60 | lb_resource_group_name = data.azurerm_resource_group.bp3.name 61 | Lb_sku = "basic" 62 | subnets_ids = module.Az-VirtualNetwork-Demo.subnet_ids 63 | lb_additional_tags = { bp = "3" } 64 | } 65 | 66 | module "Az-Vm-Demo" { 67 | source = "JamesDLD/Az-Vm/azurerm" 68 | version = "0.1.2" 69 | sa_bootdiag_storage_uri = data.azurerm_storage_account.bp3.primary_blob_endpoint #(Mandatory) 70 | subnets_ids = module.Az-VirtualNetwork-Demo.subnet_ids #(Mandatory) 71 | linux_vms = {} #(Mandatory) 72 | windows_vms = var.vms #(Mandatory) 73 | 74 | #This an implicit dependency 75 | internal_lb_backend_ids = module.Create-AzureRmLoadBalancer-Demo.lb_backend_ids 76 | 77 | #This an explicit dependency 78 | #internal_lb_backend_ids = ["/subscriptions/${var.subscription_id}/resourceGroups/infr-jdld-noprd-rg1/providers/Microsoft.Network/loadBalancers/demo-bp3-internal-lb1/backendAddressPools/demo-bp3-internal-bckpool1"] 79 | 80 | vm_resource_group_name = data.azurerm_resource_group.bp3.name 81 | vm_prefix = "bp3" #(Optional) 82 | windows_storage_image_reference = var.windows_storage_image_reference #(Optional) 83 | admin_username = var.app_admin 84 | admin_password = var.pass 85 | vm_additional_tags = { bp = "3" } #(Optional) 86 | } 87 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-3/secret/README.md: -------------------------------------------------------------------------------- 1 | Folder's golden rule 2 | ------------ 3 | - Store secrets 4 | - File containing password are ignored by any git push 5 | 6 | 7 | Secret files 8 | ------------ 9 | - backend-jdld.json : stores the credential to write Terraform backend files 10 | - Content sample ==> 11 | ```json 12 | { 13 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 14 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 15 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 16 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 17 | } 18 | ``` 19 | - main-jdld.json : stores the credential to write in Infra and Apps Resource groups and store the VM credentials 20 | - Content sample ==> 21 | ```json 22 | { 23 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 24 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 25 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 26 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 27 | "app_admin": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 28 | "pass": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 29 | } 30 | ``` -------------------------------------------------------------------------------- /Best-Practice/BestPractice-3/var.tf: -------------------------------------------------------------------------------- 1 | #Variables declaration 2 | 3 | variable "subscription_id" { 4 | description = "Azure subscription Id." 5 | } 6 | 7 | variable "tenant_id" { 8 | description = "Azure tenant Id." 9 | } 10 | 11 | variable "client_id" { 12 | description = "Azure service principal application Id" 13 | } 14 | 15 | variable "client_secret" { 16 | description = "Azure service principal application Secret" 17 | } 18 | 19 | variable "subnets" { 20 | description = "Subnet list." 21 | type = any 22 | } 23 | 24 | variable "Lbs" { 25 | description = "List containing your load balancers." 26 | type = any 27 | } 28 | variable "vms" { 29 | description = "VMs list." 30 | type = any 31 | } 32 | 33 | variable "windows_storage_image_reference" { 34 | type = map(string) 35 | description = "Could containt an 'id' of a custom image or the following parameters for an Azure public 'image publisher','offer','sku', 'version'" 36 | } 37 | 38 | variable "app_admin" { 39 | description = "Specifies the name of the administrator account on the VM." 40 | } 41 | 42 | variable "pass" { 43 | description = "Specifies the password of the administrator account on the VM." 44 | } 45 | 46 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-3/variable/main-jdld.tfvars: -------------------------------------------------------------------------------- 1 | #Variables initialization 2 | 3 | subnets = { 4 | snet1 = { 5 | vnet_key = "vnet1" #(Mandatory) 6 | name = "bp3-front-snet1" #(Mandatory) 7 | address_prefix = "10.0.3.0/24" #(Mandatory) 8 | service_endpoints = ["Microsoft.Sql", "Microsoft.Storage"] #(Optional) delete this line for no Service Endpoints 9 | } 10 | } 11 | 12 | Lbs = { 13 | lb1 = { 14 | id = "1" #Id of the load balancer use as a suffix of the load balancer name 15 | suffix_name = "internal" 16 | subnet_iteration = "0" #Id of the Subnet 17 | static_ip = "10.0.3.4" #(Optional) Set null to get dynamic IP or delete this line 18 | } 19 | } 20 | 21 | vms = { 22 | vm1 = { 23 | suffix_name = "rds" #(Mandatory) suffix of the vm 24 | id = "1" #(Mandatory) Id of the VM 25 | static_ip = "10.0.3.5" #(Optional) Set null to get dynamic IP or delete this line 26 | internal_lb_iteration = "0" #(Optional) Id of the Internal Load Balancer, set to null or delete the line if there is no Load Balancer 27 | storage_data_disks = [] #(Mandatory) For no data disks set [] 28 | subnet_iteration = "0" #(Mandatory) Id of the Subnet 29 | zones = ["1"] #Availability Zone id, could be 1, 2 or 3, if you don't need to set it to null or delete the line 30 | vm_size = "Standard_B2s" #(Mandatory) 31 | managed_disk_type = "Premium_LRS" #(Mandatory) 32 | } 33 | 34 | vm1 = { 35 | suffix_name = "rds" #(Mandatory) suffix of the vm 36 | id = "2" #(Mandatory) Id of the VM 37 | static_ip = "10.0.3.6" #(Optional) Set null to get dynamic IP or delete this line 38 | internal_lb_iteration = null #(Optional) Id of the Internal Load Balancer, set to null or delete the line if there is no Load Balancer 39 | storage_data_disks = [] #(Mandatory) For no data disks set [] 40 | subnet_iteration = "0" #(Mandatory) Id of the Subnet 41 | zones = ["1"] #Availability Zone id, could be 1, 2 or 3, if you don't need to set it to null or delete the line 42 | vm_size = "Standard_B2s" #(Mandatory) 43 | managed_disk_type = "Premium_LRS" #(Mandatory) 44 | } 45 | } 46 | 47 | windows_storage_image_reference = { 48 | publisher = "MicrosoftWindowsServer" 49 | offer = "WindowsServer" 50 | sku = "2016-Datacenter" 51 | version = "Latest" 52 | } 53 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-4/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | *.DS_Store 5 | 6 | # Directory 7 | .terraform/ 8 | 9 | # Secret files 10 | secret/*.tfvars 11 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-4/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | Best Practice 4 4 | ------------ 5 | These are my recommandations concerning the usage of the `azurerm_template_deployment` Terraform resource : 6 | 1. Don't use the `azurerm_template_deployment` Terraform resource 7 | 2. If you don't have the choice because one Terraform resource doesn't exist 8 | * Create a feature request [here on GitHub](https://github.com/terraform-providers/terraform-provider-azurerm/issues/new/choose) 9 | * Use the `azurerm_template_deployment` Terraform resource (a demo will be shown in this article) 10 | 11 | "Terraform can only manage the deployment of the ARM Template - and not any resources which are created by it. It's highly recommended using the Native Resources where possible instead rather than an ARM Template" (see [this article](https://www.terraform.io/docs/providers/azurerm/r/template_deployment.html) from terraform.io website for more information). 12 | 13 | In this article we will perform the following action with : 14 | 1. Create a Standard Public Load Balancer with outbound rules 15 | * Microsoft associated documentation : [Azure Load balancer outbound rules overview](https://docs.microsoft.com/en-us/azure/load-balancer/load-balancer-outbound-rules-overview) 16 | * Azure Rm Template : [azuredeploy.json](https://jamesdld.github.io/AzureRm-Template/Create-AzureRmLoadBalancerOutboundRules/) 17 | 18 | 19 | ### Prerequisite 20 | ----- 21 | 22 | | Item | Description | 23 | | ------------- | ------------- | 24 | | Azure Subscription | An Azure subscription id | 25 | | Resource Group | An Azure resource group is available | 26 | | Storage Account | An Azure storage account is available and is located in the upper resource group, it contains a container named `tfstate` | 27 | | Service Principal | An Azure service principal is available and has the `owner` privilege on the upper resource group | 28 | | Terraform file | [Clone this repository](https://github.com/JamesDLD/terraform/tree/master/Best-Practice/BestPractice-4) and fill in the following files with the upper prerequisite items :
Variable used for the Terraform `init` : secret/backend-jdld.json
Variable used for the Terraform `plan` and `apply` : [main.tf](main.tf) & [main-jdld.tfvars](variable/main-jdld.tfvars) & secret/main-jdld.json | 29 | 30 | 31 | 32 | What should we do? 33 | ------------ 34 | We will create the upper mentioned element using remote backend (see the previous article [BestPractice-1](../BestPractice-1) for more information about remote backend). 35 | 36 | Review the [main.tf file of the module here](https://github.com/JamesDLD/terraform/tree/master/module/Add-AzureRmLoadBalancerOutboundRules). 37 | As illustrated in the following bracket that's how we can push parameters into an AzureRm template (this file template is located here : [azuredeploy.json](https://github.com/JamesDLD/AzureRm-Template/tree/master/Create-AzureRmLoadBalancerOutboundRules) 38 | ```hcl 39 | resource "azurerm_template_deployment" "lb_to_addOutboundRule" { 40 | name = "${var.lbs_out[0]["suffix_name"]}-bck-DEP" 41 | resource_group_name = var.lb_out_resource_group_name 42 | template_body = file( 43 | "${path.module}/AzureRmLoadBalancerOutboundRules_template.json", 44 | ) 45 | deployment_mode = "Incremental" 46 | 47 | parameters = { 48 | lbName = "${var.lb_out_prefix}${var.lbs_out[0]["suffix_name"]}${var.lb_out_suffix}" 49 | tags = jsonencode(var.lbs_tags) 50 | sku = var.lbs_out[0]["sku"] 51 | allocatedOutboundPorts = var.lbs_out[0]["allocatedOutboundPorts"] 52 | idleTimeoutInMinutes = var.lbs_out[0]["idleTimeoutInMinutes"] 53 | enableTcpReset = var.lbs_out[0]["enableTcpReset"] 54 | protocol = var.lbs_out[0]["protocol"] 55 | lb_public_ip_id = var.lb_public_ip_id 56 | } 57 | } 58 | ``` 59 | 60 | ### 1. Usage 61 | ----- 62 | 63 | This step ensures that Terraform has all the prerequisites to build your template in Azure. 64 | ```hcl 65 | terraform init -backend-config="secret/backend-jdld.json" -reconfigure 66 | ``` 67 | 68 | The Terraform plan command is used to create an execution plan. 69 | This step compares the requested resources to the state information saved by Terraform and then gives as an output the planned execution. Resources are not created in Azure. 70 | ```hcl 71 | terraform plan -var-file="secret/main-jdld.json" -var-file="variable/main-jdld.tfvars" 72 | ``` 73 | 74 | If all is ok with the proposal you can now apply the configuration. 75 | ```hcl 76 | terraform apply -var-file="secret/main-jdld.json" -var-file="variable/main-jdld.tfvars" 77 | ``` 78 | 79 | We will now destroy what we have done and you will see that our load balancer will not be deleted. 80 | ```hcl 81 | terraform destroy -var-file="secret/main-jdld.json" -var-file="variable/main-jdld.tfvars" 82 | ``` 83 | 84 | ### 2. Analysis 85 | ----- 86 | 87 | | Description | Screenshot | 88 | | ------------- | ------------- | 89 | | Have a look on the deployment on Azure | ![depl](image/depl.png) | 90 | | When processing a Terraform `destroy` our deployment object is being deleted | ![destroy](image/destroy.png) | 91 | | After processing a Terraform `destroy` our load balancer is still available in Azure | ![nodestroy](image/nodestroy.png) | 92 | 93 | 94 | See you! 95 | 96 | JamesDLD -------------------------------------------------------------------------------- /Best-Practice/BestPractice-4/image/depl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-4/image/depl.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-4/image/destroy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-4/image/destroy.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-4/image/nodestroy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/Best-Practice/BestPractice-4/image/nodestroy.png -------------------------------------------------------------------------------- /Best-Practice/BestPractice-4/main.tf: -------------------------------------------------------------------------------- 1 | #Set the terraform backend 2 | terraform { 3 | required_version = ">= 0.12.6" 4 | 5 | backend "azurerm" { 6 | storage_account_name = "infrasdbx1vpcjdld1" 7 | container_name = "tfstate" 8 | key = "BestPractice-4.tfstate" 9 | resource_group_name = "infr-jdld-noprd-rg1" 10 | } 11 | } 12 | 13 | #Set the Provider 14 | provider "azurerm" { 15 | version = "~> 2.0" #Use the last version tested through an Azure DevOps pipeline here : https://dev.azure.com/jamesdld23/vpc_lab/_build 16 | subscription_id = var.subscription_id 17 | client_id = var.client_id 18 | client_secret = var.client_secret 19 | tenant_id = var.tenant_id 20 | features {} 21 | } 22 | 23 | #Call module/resource 24 | data "azurerm_resource_group" "infr" { 25 | name = "infr-jdld-noprd-rg1" 26 | } 27 | 28 | #Action 29 | module "Add-AzureRmLoadBalancerOutboundRules-Apps" { 30 | source = "git::https://github.com/JamesDLD/terraform.git//module/Add-AzureRmLoadBalancerOutboundRules?ref=master" 31 | lbs_out = var.lbs_public 32 | lb_out_prefix = "bp4-" 33 | lb_out_suffix = "-publiclb1" 34 | lb_out_resource_group_name = data.azurerm_resource_group.infr.name 35 | lbs_tags = data.azurerm_resource_group.infr.tags 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-4/secret/README.md: -------------------------------------------------------------------------------- 1 | Folder's golden rule 2 | ------------ 3 | - Store secrets 4 | - File containing password are ignored by any git push 5 | 6 | 7 | Secret files 8 | ------------ 9 | - backend-jdld.json : stores the credential to write Terraform backend files 10 | - Content sample ==> 11 | ```json 12 | { 13 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 14 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 15 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 16 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 17 | } 18 | ``` 19 | - main-jdld.json : stores the credential to write in Infra and Apps Resource groups and store the VM credentials 20 | - Content sample ==> 21 | ```json 22 | { 23 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 24 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 25 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 26 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 27 | } 28 | ``` -------------------------------------------------------------------------------- /Best-Practice/BestPractice-4/var.tf: -------------------------------------------------------------------------------- 1 | #Variables declaration 2 | 3 | variable "subscription_id" { 4 | description = "Azure subscription Id." 5 | } 6 | 7 | variable "tenant_id" { 8 | description = "Azure tenant Id." 9 | } 10 | 11 | variable "client_id" { 12 | description = "Azure service principal application Id" 13 | } 14 | 15 | variable "client_secret" { 16 | description = "Azure service principal application Secret" 17 | } 18 | 19 | variable "lbs_public" { 20 | description = "Load balancer properties." 21 | type = list(object({ 22 | suffix_name = string 23 | sku = string 24 | allocatedOutboundPorts = number #Number of SNAT ports, Load Balancer allocates SNAT ports in multiples of 8. 25 | idleTimeoutInMinutes = number #Outbound flow idle timeout. The parameter accepts a value from 4 to 66. 26 | enableTcpReset = bool #Enable TCP Reset on idle timeout. 27 | protocol = string #Transport protocol of the outbound rule. 28 | })) 29 | } 30 | -------------------------------------------------------------------------------- /Best-Practice/BestPractice-4/variable/main-jdld.tfvars: -------------------------------------------------------------------------------- 1 | #Variables initialization 2 | 3 | lbs_public = [ 4 | { 5 | suffix_name = "outbound" 6 | sku = "Standard" 7 | allocatedOutboundPorts = "8" #Number of SNAT ports, Load Balancer allocates SNAT ports in multiples of 8. 8 | idleTimeoutInMinutes = "4" #Outbound flow idle timeout. The parameter accepts a value from 4 to 66. 9 | enableTcpReset = "false" #Enable TCP Reset on idle timeout. 10 | protocol = "Tcp" #Transport protocol of the outbound rule. 11 | }, 12 | ] 13 | -------------------------------------------------------------------------------- /Best-Practice/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | [![Build Status](https://dev.azure.com/jamesdld23/vpc_lab/_apis/build/status/JamesDLD.terraform%20BP?branchName=master)](https://dev.azure.com/jamesdld23/vpc_lab/_build/latest?definitionId=5&branchName=master) 4 | 5 | Content 6 | ------------ 7 | 8 | Share a list of best practices and tutoriels when using Terraform on Azure. 9 | 10 | Best Practice 11 | ------------ 12 | 13 | | Id | Description | 14 | | ------------- | ------------- | 15 | | [BestPractice-1](BestPractice-1) | Use remote backend on Azure | 16 | | [BestPractice-2](BestPractice-2) | Manage Terraform, Azure Rm provider and modules version | 17 | | [BestPractice-3](BestPractice-3) | Use implicit dependencies | 18 | | [BestPractice-4](BestPractice-4) | **Warning** concering the resource `azurerm_template_deployment` | 19 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/CreateAzureRm-Infra.yml: -------------------------------------------------------------------------------- 1 | #Multi-stage YAML pipeline. 2 | name: $(BuildDefinitionName).$(DayOfYear)$(Rev:.r) 3 | 4 | schedules: 5 | - cron: "45 5 * * 4" 6 | branches: 7 | include: 8 | - master 9 | displayName: Weekly Thursday 5h45 am UTC build 10 | always: true 11 | 12 | variables: 13 | - group: terraform_binary # variable group containing Terraform information like the Terraform version (like terraform_version) 14 | - name: vmImageName 15 | value: "ubuntu-latest" 16 | - name: backend_main_secret_file_id1 # secret file used by the following cmdlet Terraform init, plan, apply and destroy 17 | value: "backend-main-jdld-1.json" 18 | - name: main_secret_file_id2 # secret file used by the following cmdlet Terraform init, plan, apply and destroy 19 | value: "main-jdld-2.tfvars" 20 | - name: artifact_name1 21 | value: "CreateAzureRm-Infra1" 22 | - name: artifact_name2 23 | value: "CreateAzureRm-Infra2" 24 | 25 | resources: 26 | repositories: 27 | - repository: Yaml_Templates # identifier (A-Z, a-z, 0-9, and underscore) 28 | type: github 29 | endpoint: JamesDLD # name of the service connection to use (for non-Azure Repos types) 30 | name: JamesDLD/terraform 31 | #ref: refs/tags/0.0.1 # ref name to use, defaults to 'refs/heads/master' 32 | 33 | trigger: 34 | batch: true # when a build is running, the system waits until the build is completed 35 | branches: 36 | include: 37 | - master 38 | - feature/* 39 | - release/* 40 | paths: 41 | include: 42 | - CreateAzureRm-Infra/* 43 | 44 | stages: 45 | - stage: Build_Infra 46 | jobs: 47 | - job: Terraform_Plan_Infra 48 | displayName: Terraform Plan - Infra - Publish a package if Infrastructure changes are identified 49 | continueOnError: false 50 | pool: 51 | vmImage: $(vmImageName) 52 | steps: 53 | - task: DownloadSecureFile@1 54 | displayName: "Download secure file $(backend_main_secret_file_id1)" 55 | inputs: 56 | secureFile: $(backend_main_secret_file_id1) 57 | 58 | - task: DownloadSecureFile@1 59 | displayName: "Download secure file $(main_secret_file_id2)" 60 | inputs: 61 | secureFile: $(main_secret_file_id2) 62 | 63 | - template: pipeline/terraform.yml@Yaml_Templates 64 | parameters: 65 | version: $(terraform_version) 66 | path: "./CreateAzureRm-Infra/infra/" 67 | package_name: "Infra" 68 | terraform_init: true 69 | terraform_plan: true 70 | backend_file_path: "variable/infra-backend-jdld.tfvars" 71 | backend_secret_file_id: $(backend_main_secret_file_id1) 72 | main_secret_file_id: $(main_secret_file_id2) 73 | main_file_path: "variable/infra-main-jdld.tfvars" 74 | 75 | - publish: "./ArtifactPublishLocation" # Local path to include in the Artifact 76 | artifact: "$(artifact_name1)" 77 | 78 | - stage: Deploy_Infra 79 | dependsOn: Build_Infra 80 | jobs: 81 | # track deployments on the environment 82 | - deployment: Terraform_Apply_Infra 83 | displayName: Terraform Apply - Infra - Resources creation 84 | pool: 85 | vmImage: $(vmImageName) 86 | environment: "Terraform_Apply" 87 | strategy: 88 | # default deployment strategy 89 | runOnce: 90 | deploy: 91 | steps: 92 | - template: pipeline/terraform.yml@Yaml_Templates 93 | parameters: 94 | version: $(terraform_version) 95 | artifact_path: $(Pipeline.Workspace)/$(artifact_name1) 96 | package_name: "Infra" 97 | terraform_apply: true 98 | 99 | - stage: Build_Apps 100 | dependsOn: Deploy_Infra 101 | jobs: 102 | - job: Terraform_Plan_Apps 103 | displayName: Terraform Plan - Apps - Publish a package if Infrastructure changes are identified 104 | continueOnError: false 105 | pool: 106 | vmImage: $(vmImageName) 107 | steps: 108 | - task: DownloadSecureFile@1 109 | displayName: "Download secure file $(backend_main_secret_file_id1)" 110 | inputs: 111 | secureFile: $(backend_main_secret_file_id1) 112 | 113 | - task: DownloadSecureFile@1 114 | displayName: "Download secure file $(main_secret_file_id2)" 115 | inputs: 116 | secureFile: $(main_secret_file_id2) 117 | 118 | - template: pipeline/terraform.yml@Yaml_Templates 119 | parameters: 120 | version: $(terraform_version) 121 | path: "./CreateAzureRm-Infra/apps/" 122 | package_name: "Apps" 123 | terraform_init: true 124 | terraform_plan: true 125 | backend_file_path: "variable/apps-backend-jdld.tfvars" 126 | backend_secret_file_id: $(backend_main_secret_file_id1) 127 | main_secret_file_id: $(main_secret_file_id2) 128 | main_file_path: "variable/apps-main-jdld.tfvars" 129 | 130 | - publish: "./ArtifactPublishLocation" # Local path to include in the Artifact 131 | artifact: "$(artifact_name2)" 132 | 133 | - stage: Deploy_Apps 134 | dependsOn: Build_Apps 135 | jobs: 136 | # track deployments on the environment 137 | - deployment: Terraform_Apply_Apps 138 | displayName: Terraform Apply - Apps - Resources creation 139 | pool: 140 | vmImage: $(vmImageName) 141 | environment: "Terraform_Apply" 142 | strategy: 143 | # default deployment strategy 144 | runOnce: 145 | deploy: 146 | steps: 147 | - template: pipeline/terraform.yml@Yaml_Templates 148 | parameters: 149 | version: $(terraform_version) 150 | artifact_path: $(Pipeline.Workspace)/$(artifact_name2) 151 | package_name: "Apps" 152 | terraform_apply: true 153 | 154 | - stage: Deliver 155 | dependsOn: Deploy_Apps 156 | jobs: 157 | # track deployments on the environment 158 | - deployment: Terraform_Destroy 159 | displayName: Terraform Destroy - Script ok, now deleting the resources 160 | pool: 161 | vmImage: $(vmImageName) 162 | environment: "Terraform_Destroy" 163 | strategy: 164 | # default deployment strategy 165 | runOnce: 166 | deploy: 167 | steps: 168 | - task: DownloadSecureFile@1 169 | displayName: "Download secure file $(main_secret_file_id2)" 170 | inputs: 171 | secureFile: $(main_secret_file_id2) 172 | 173 | - task: DownloadSecureFile@1 174 | displayName: "Download secure file $(backend_main_secret_file_id1)" 175 | inputs: 176 | secureFile: $(backend_main_secret_file_id1) 177 | 178 | - template: pipeline/terraform.yml@Yaml_Templates 179 | parameters: 180 | version: $(terraform_version) 181 | artifact_path: $(Pipeline.Workspace)/$(artifact_name2) 182 | package_name: "Apps" 183 | terraform_destroy: true 184 | main_secret_file_id: $(main_secret_file_id2) 185 | main_file_path: "variable/apps-main-jdld.tfvars" 186 | 187 | - template: pipeline/terraform.yml@Yaml_Templates 188 | parameters: 189 | version: $(terraform_version) 190 | artifact_path: $(Pipeline.Workspace)/$(artifact_name1) 191 | package_name: "Infra" 192 | terraform_destroy: true 193 | main_secret_file_id: $(main_secret_file_id2) 194 | main_file_path: "variable/infra-main-jdld.tfvars" 195 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | [![Build Status](https://dev.azure.com/jamesdld23/vpc_lab/_apis/build/status/JamesDLD.terraform%20VPC?branchName=master)](https://dev.azure.com/jamesdld23/vpc_lab/_build/latest?definitionId=6&branchName=master) 4 | 5 | Azure and Terraform 6 | ------------ 7 | Simple and Powerful 8 | 9 | HashiCorp Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned. 10 | 11 | 12 | About the [Terraform's modules](https://registry.terraform.io/modules/JamesDLD) 13 | ------------ 14 | On of the objective here is to share Terraform custom modules with the community with the following guidelines : 15 | - a module is used when we need to call a given number of resources several times and the same way, for exemple : when creating a VM we need nic, disks, backup, log monitoring, etc .. 16 | - a module doesn't contain any static values 17 | - a module is called using variables 18 | 19 | Workflow 20 | ------------ 21 | ![Workflow](workflow.png) 22 | 23 | Usage 24 | ----- 25 | 26 | | Step | Description | 27 | | ------------- | ------------- | 28 | | [1 - Infra](infra) | Deliver the Infra | 29 | | [2 - Apps](apps) | Deliver an Apps environment | 30 | 31 | General Requirements 32 | ------------ 33 | 34 | - [Terraform](https://www.Terraform.io/downloads.html) 0.12.6 35 | - [AzureRM Terraform Provider](https://github.com/Terraform-providers/Terraform-provider-azurerm/blob/master/README.md) 36 | - [AzureRM Terraform Provider - Authentication](https://www.Terraform.io/docs/providers/azurerm/) 37 | - The called "Infra" Azure Service Principal has the following privileges : 38 | - Owner privilege at Azure Subscription level (mandatory to create custom roles) 39 | - Read directory data on Windows Azure Active Directory (mandatory to assign custom roles) 40 | - The called "Apps" Azure Service Principal has the following privilege : 41 | - Reader privilege at Azure Subscription level (mandatory to use terraform data source ressource) 42 | 43 | Golden rules 44 | ------------ 45 | - Always use Terraform implicit dependency, evict the use of the depends_on argument, [see Terraform dependencies article for more info](https://www.terraform.io/intro/getting-started/dependencies.html) 46 | - Use remote backends to save your Terraform state, [see Terraform remote backends article for more info](https://www.terraform.io/intro/getting-started/remote.html) 47 | 48 | Improvment & Feature request & Limitation 49 | ------------ 50 | - Even with the use of implicit dependency, the script doesn't wait enough between the privileges setting for Apps SPN and the when Apps SPN creates it's objects, for this reason the script will raise a privilege error at the first time, you will have to relaunch it to have a full success of all operations. After this, the Apps SPN doesn't need anymore to have the Reader privilege at the subscription level. [This might me solved by with feature "timeout" explained here](https://github.com/terraform-providers/terraform-provider-azurerm/issues/2807). 51 | - Terraform authentication to AzureRM via Service Principal & certificate 52 | - Feature Request: resource azurerm_automation_variable, [ticket raised here](https://github.com/terraform-providers/terraform-provider-azurerm/issues/1312) 53 | 54 | Solved issues 55 | ------------ 56 | - Use multiple Azure service principal through the provider AzureRm, [ticket raised here](https://github.com/terraform-providers/terraform-provider-azurerm/issues/1308) 57 | - Solution : usage of provider.azurerm v1.6.0 58 | - Use condition to decide wether or not a NIC should be linked to a Load Balancer, [ticket raised here](https://github.com/terraform-providers/terraform-provider-azurerm/issues/1318) 59 | - Solution : usage of map and the foreach feature 60 | - Terraform resource for AzureRm recovery services is now available, we can use the native resource since provider.azurerm v1.4.0. [change log is available here for info](https://github.com/terraform-providers/terraform-provider-azurerm/blob/master/CHANGELOG.md) 61 | - Set the BackupStorageRedundancy paremeter (LRS or GRS) in the RecoveryServices/vaults template, [Microsoft.RecoveryServices/vaults template reference](https://docs.microsoft.com/en-us/azure/templates/microsoft.recoveryservices/vaults) 62 | - Solution : use sku Standard, RS0, [Terraform resource azurerm_recovery_services_vault](https://www.terraform.io/docs/providers/azurerm/r/recovery_services_vault.html) 63 | - [Work still needed on Microsoft API to use RS0 (LRS) at the RSV creation](https://github.com/Azure/azure-rest-api-specs/issues/4901) -------------------------------------------------------------------------------- /CreateAzureRm-Infra/apps/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | Step 2 : Deliver an Apps environment 4 | ----- 5 | 6 | With your Terraform template created, the first step is to initialize Terraform. 7 | This step ensures that Terraform has all the prerequisites to build your template in Azure. 8 | 9 | ```hcl 10 | 11 | terraform init -backend-config="variable/apps-backend-jdld.tfvars" -backend-config="../secret/backend-jdld.json" -reconfigure 12 | 13 | ``` 14 | 15 | The terraform plan command is used to create an execution plan. 16 | This step compares the requested resources to the state information saved by Terraform and then gives as an output the planned execution. Resources are not created in Azure. 17 | ```hcl 18 | 19 | terraform plan -var-file="variable/apps-main-jdld.tfvars" -var-file="../secret/main-jdld.tfvars" 20 | 21 | ``` 22 | 23 | If all is ok with the proposal you can now apply the configuration. 24 | ```hcl 25 | 26 | terraform apply -var-file="variable/apps-main-jdld.tfvars" -var-file="../secret/main-jdld.tfvars" 27 | 28 | ``` 29 | 30 | To destroy the associated resources. 31 | ```hcl 32 | 33 | terraform destroy -var-file="variable/apps-main-jdld.tfvars" -var-file="../secret/main-jdld.tfvars" 34 | 35 | ``` -------------------------------------------------------------------------------- /CreateAzureRm-Infra/apps/main.tf: -------------------------------------------------------------------------------- 1 | # Providers (Infra & Apps) 2 | 3 | provider "azurerm" { 4 | subscription_id = var.subscription_id 5 | client_id = var.service_principals[1]["Application_Id"] 6 | client_secret = var.service_principals[1]["Application_Secret"] 7 | tenant_id = var.tenant_id 8 | version = "~> 2.0" 9 | features {} 10 | } 11 | 12 | # Call Resources and Modules 13 | 14 | #################################################### 15 | ########## Infra ########## 16 | #################################################### 17 | 18 | ## Prerequisistes Inventory 19 | data "azurerm_resource_group" "Infr" { 20 | name = var.rg_infr_name 21 | } 22 | 23 | data "azurerm_storage_account" "Infr" { 24 | name = var.sa_infr_name 25 | resource_group_name = var.rg_infr_name 26 | } 27 | 28 | data "azurerm_recovery_services_vault" "vault" { 29 | name = var.bck_rsv_name 30 | resource_group_name = data.azurerm_resource_group.Infr.name 31 | } 32 | 33 | data "azurerm_subnet" "snets" { 34 | count = length(var.apps_snets) 35 | name = var.apps_snets[count.index]["subnet_name"] 36 | virtual_network_name = var.apps_snets[count.index]["vnet_name"] 37 | resource_group_name = data.azurerm_resource_group.Infr.name 38 | } 39 | 40 | #################################################### 41 | ########## Apps ########## 42 | #################################################### 43 | 44 | ## Prerequisistes Inventory 45 | data "azurerm_resource_group" "MyApps" { 46 | name = var.rg_apps_name 47 | } 48 | 49 | ## Core Network components 50 | 51 | resource "azurerm_network_security_group" "apps_nsgs" { 52 | for_each = var.apps_nsgs 53 | name = "${var.app_name}-${var.env_name}-nsg${each.value["id"]}" 54 | location = data.azurerm_resource_group.MyApps.location 55 | resource_group_name = data.azurerm_resource_group.MyApps.name 56 | 57 | dynamic "security_rule" { 58 | for_each = each.value["security_rules"] 59 | content { 60 | description = lookup(security_rule.value, "description", null) 61 | direction = lookup(security_rule.value, "direction", null) 62 | name = lookup(security_rule.value, "name", null) 63 | access = lookup(security_rule.value, "access", null) 64 | priority = lookup(security_rule.value, "priority", null) 65 | source_address_prefix = lookup(security_rule.value, "source_address_prefix", null) 66 | source_address_prefixes = lookup(security_rule.value, "source_address_prefixes", null) 67 | destination_address_prefix = lookup(security_rule.value, "destination_address_prefix", null) 68 | destination_address_prefixes = lookup(security_rule.value, "destination_address_prefixes", null) 69 | destination_port_range = lookup(security_rule.value, "destination_port_range", null) 70 | destination_port_ranges = lookup(security_rule.value, "destination_port_ranges", null) 71 | protocol = lookup(security_rule.value, "protocol", null) 72 | source_port_range = lookup(security_rule.value, "source_port_range", null) 73 | source_port_ranges = lookup(security_rule.value, "source_port_ranges", null) 74 | } 75 | } 76 | tags = data.azurerm_resource_group.MyApps.tags 77 | } 78 | 79 | 80 | ## Virtual Machines components 81 | 82 | module "Create-AzureRmLoadBalancer-Apps" { 83 | source = "JamesDLD/Az-LoadBalancer/azurerm" 84 | version = "0.1.1" 85 | Lbs = var.Lbs 86 | LbRules = var.LbRules 87 | lb_prefix = "${var.app_name}-${var.env_name}" 88 | lb_location = data.azurerm_resource_group.MyApps.location 89 | lb_resource_group_name = data.azurerm_resource_group.MyApps.name 90 | Lb_sku = var.Lb_sku 91 | subnets_ids = data.azurerm_subnet.snets.*.id 92 | lb_additional_tags = { iac = "Terraform" } 93 | } 94 | 95 | module "Az-Vm" { 96 | source = "JamesDLD/Az-Vm/azurerm" 97 | version = "0.1.2" 98 | sa_bootdiag_storage_uri = data.azurerm_storage_account.Infr.primary_blob_endpoint #(Mandatory) 99 | subnets_ids = data.azurerm_subnet.snets.*.id #(Mandatory) 100 | linux_vms = var.linux_vms #(Mandatory) 101 | windows_vms = var.windows_vms #(Mandatory) 102 | vm_resource_group_name = data.azurerm_resource_group.MyApps.name 103 | vm_prefix = "" #(Optional) 104 | admin_username = var.app_admin 105 | admin_password = var.pass 106 | ssh_key = var.ssh_key 107 | enable_log_analytics_dependencies = false #(Optional) Default is false 108 | recovery_services_vault_name = data.azurerm_recovery_services_vault.vault.name #(Optional) 109 | recovery_services_vault_rgname = data.azurerm_resource_group.Infr.name #(Optional) Use the RG's location if not set 110 | nsgs_ids = [for x in azurerm_network_security_group.apps_nsgs : x.id] #(Optional) 111 | internal_lb_backend_ids = module.Create-AzureRmLoadBalancer-Apps.lb_backend_ids #(Optional) 112 | vm_additional_tags = { iac = "Terraform" } 113 | 114 | #All other optional values 115 | /* 116 | key_vault_name = var.key_vault_name #(Optional) 117 | key_vault_rgname = data.azurerm_resource_group.Infr.name #(Optional) Use the RG's location if not set 118 | vm_location = element(module.Az-VirtualNetwork-Demo.vnet_locations, 0) #(Optional) Use the RG's location if not set 119 | workspace_name = azurerm_log_analytics_workspace.Apps.name #(Optional) 120 | workspace_resource_rgname = "" #(Optional) Use the RG's location if not set 121 | public_ip_ids = module.Az-VirtualNetwork-Demo.public_ip_ids #(Optional) 122 | public_lb_backend_ids = ["public_backend_id1", "public_backend_id1"] #(Optional) 123 | */ 124 | 125 | } 126 | # Infra cross services for Apps 127 | #N/A 128 | 129 | ## Infra common services 130 | #N/A 131 | 132 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/apps/variable/apps-backend-jdld.tfvars: -------------------------------------------------------------------------------- 1 | #Variables initialization 2 | storage_account_name = "infrasdbx1vpcjdld1" 3 | 4 | container_name = "tfstate" 5 | 6 | key = "apps.tfstate" 7 | 8 | resource_group_name = "infr-jdld-noprd-rg1" 9 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/apps/variable/apps-main-jdld.tfvars: -------------------------------------------------------------------------------- 1 | #Variables initialization 2 | 3 | #Common variables 4 | app_name = "jdld" 5 | 6 | env_name = "apps" 7 | 8 | #Below tag variable is no more used as this data is now probed trhough the data source module "Get-AzureRmResourceGroup" 9 | default_tags = { 10 | ENV = "sand1" 11 | APP = "JDLD" 12 | BUD = "FR_BXXXXX" 13 | CTC = "j.dumont@veebaze.com" 14 | } 15 | 16 | rg_apps_name = "apps-jdld-sand1-rg1" 17 | 18 | rg_infr_name = "infr-jdld-noprd-rg1" 19 | 20 | #Storage 21 | sa_infr_name = "infrasdbx1vpcjdld1" 22 | 23 | #Backup 24 | bck_rsv_name = "jdld-infr-rsv1" 25 | 26 | #Network 27 | 28 | apps_snets = [ 29 | { 30 | vnet_name = "jdld-infr-apps-vnet1" 31 | subnet_name = "front1" 32 | }, 33 | { 34 | vnet_name = "jdld-infr-apps-vnet1" 35 | subnet_name = "back1" 36 | }, 37 | ] 38 | 39 | apps_nsgs = { 40 | 41 | default_nsg1 = { 42 | id = "1" 43 | security_rules = [ 44 | { 45 | description = "Demo1" 46 | direction = "Inbound" 47 | name = "ALL_to_NIC_tcp-3389" 48 | access = "Allow" 49 | priority = "2000" 50 | source_address_prefix = "*" 51 | destination_address_prefix = "*" 52 | destination_port_range = "3389" 53 | protocol = "tcp" 54 | source_port_range = "*" 55 | }, 56 | { 57 | direction = "Inbound" 58 | name = "ALL_to_NIC_tcp-80-443" 59 | access = "Allow" 60 | priority = "2001" 61 | source_address_prefix = "*" 62 | destination_address_prefix = "*" 63 | destination_port_range = "80" 64 | protocol = "tcp" 65 | source_port_range = "*" 66 | }, 67 | { 68 | direction = "Outbound" 69 | name = "ALL_to_GoogleDns_udp-53" 70 | access = "Allow" 71 | priority = "2001" 72 | source_address_prefix = "*" 73 | destination_address_prefix = "8.8.8.8" 74 | destination_port_range = "53" 75 | protocol = "*" 76 | source_port_range = "*" 77 | }, 78 | ] 79 | } 80 | } 81 | 82 | # Virtual Machines components : Load Balancer & Availability Zone & Nic & VM 83 | Lb_sku = "Standard" #"Basic" 84 | 85 | Lbs = { 86 | 87 | iis1 = { 88 | id = "1" #Id of the load balancer use as a suffix of the load balancer name 89 | suffix_name = "iis" 90 | subnet_iteration = "0" #Id of the Subnet 91 | static_ip = "10.0.2.4" 92 | } 93 | 94 | rdg1 = { 95 | id = "1" #Id of the load balancer use as a suffix of the load balancer name 96 | suffix_name = "rdg" 97 | subnet_iteration = "1" #Id of the Subnet 98 | static_ip = "10.0.2.68" 99 | } 100 | 101 | } 102 | 103 | LbRules = { 104 | 105 | iis_lbrule1 = { 106 | Id = "1" #Id of a the rule within the Load Balancer 107 | lb_key = "iis1" #Id of the Load Balancer 108 | suffix_name = "iis" #MUST match the Lbs suffix_name 109 | lb_port = "80" 110 | backend_port = "80" 111 | probe_port = "80" 112 | probe_protocol = "Http" 113 | request_path = "/" 114 | load_distribution = "Default" 115 | } 116 | 117 | rdg_lbrule1 = { 118 | Id = "1" #Id of a the rule within the Load Balancer 119 | lb_key = "rdg1" #Id of the Load Balancer 120 | suffix_name = "rdg" #MUST match the Lbs suffix_name 121 | lb_port = "443" 122 | backend_port = "443" 123 | probe_port = "443" 124 | probe_protocol = "Tcp" 125 | request_path = "" 126 | load_distribution = "Default" 127 | } 128 | 129 | } 130 | 131 | windows_vms = { 132 | 133 | vm1 = { 134 | suffix_name = "iis" #(Mandatory) suffix of the vm 135 | id = "1" #(Mandatory) Id of the VM 136 | internal_lb_iteration = "0" #(Optional) Id of the Internal Load Balancer, set to null or delete the line if there is no Load Balancer 137 | storage_data_disks = [] #(Mandatory) For no data disks set [] 138 | subnet_iteration = "0" #(Mandatory) Id of the Subnet 139 | security_group_iteration = "1" #(Optional) Id of the Network Security Group 140 | static_ip = "10.0.2.8" #(Optional) Set null to get dynamic IP or delete this line 141 | zones = ["1"] #Availability Zone id, could be 1, 2 or 3, if you don't need to set it to "", WARNING you could not have Availabilitysets and AvailabilityZones 142 | vm_size = "Standard_B2ms" #(Mandatory) 143 | managed_disk_type = "Premium_LRS" #(Mandatory) 144 | } 145 | 146 | vm2 = { 147 | suffix_name = "rdg" #(Mandatory) suffix of the vm 148 | id = "1" #(Mandatory) Id of the VM 149 | internal_lb_iteration = "1" #(Optional) Id of the Internal Load Balancer, set to null or delete the line if there is no Load Balancer 150 | storage_data_disks = [] #(Mandatory) For no data disks set [] 151 | subnet_iteration = "1" #(Mandatory) Id of the Subnet 152 | security_group_iteration = "1" #(Optional) Id of the Network Security Group 153 | static_ip = "10.0.2.72" #(Optional) Set null to get dynamic IP or delete this line 154 | zones = ["2"] #Availability Zone id, could be 1, 2 or 3, if you don't need to set it to "", WARNING you could not have Availabilitysets and AvailabilityZones 155 | vm_size = "Standard_B2ms" #(Mandatory) 156 | managed_disk_type = "Premium_LRS" #(Mandatory) 157 | #backup_policy_name = "BackupPolicy-Schedule1" #(Optional) Set null to disable backup (WARNING, this will delete previous backup) otherwise set a backup policy like BackupPolicy-Schedule1 158 | } 159 | 160 | } 161 | 162 | linux_vms = { 163 | 164 | vm1 = { 165 | suffix_name = "ssh" #(Mandatory) suffix of the vm 166 | id = "1" #(Mandatory) Id of the VM 167 | storage_data_disks = [ 168 | { 169 | id = "1" #Disk id 170 | lun = "0" 171 | disk_size_gb = "32" 172 | managed_disk_type = "Premium_LRS" 173 | caching = "ReadWrite" 174 | create_option = "Empty" 175 | }, 176 | ] #(Mandatory) For no data disks set [] 177 | subnet_iteration = "0" #(Mandatory) Id of the Subnet 178 | security_group_iteration = "1" #(Optional) Id of the Network Security Group 179 | static_ip = "10.0.2.9" #(Optional) Set null to get dynamic IP or delete this line 180 | zones = ["1"] #Availability Zone id, could be 1, 2 or 3, if you don't need to set it to "", WARNING you could not have Availabilitysets and AvailabilityZones 181 | #backup_policy_name = "BackupPolicy-Schedule1" #(Optional) Set null to disable backup (WARNING, this will delete previous backup) otherwise set a backup policy like BackupPolicy-Schedule1 182 | vm_size = "Standard_B2ms" #(Mandatory) 183 | managed_disk_type = "Premium_LRS" #(Mandatory) 184 | } 185 | 186 | vm2 = { 187 | suffix_name = "ssh" #(Mandatory) suffix of the vm 188 | id = "2" #(Mandatory) Id of the VM 189 | storage_data_disks = [] #(Mandatory) For no data disks set [] 190 | subnet_iteration = "1" #(Mandatory) Id of the Subnet 191 | security_group_iteration = "1" #(Optional) Id of the Network Security Group 192 | static_ip = "10.0.2.73" #(Optional) Set null to get dynamic IP or delete this line 193 | zones = ["2"] #Availability Zone id, could be 1, 2 or 3, if you don't need to set it to "", WARNING you could not have Availabilitysets and AvailabilityZones 194 | vm_size = "Standard_B2ms" #(Mandatory) 195 | managed_disk_type = "Premium_LRS" #(Mandatory) 196 | } 197 | 198 | } 199 | 200 | ## Infra common services 201 | #N/A 202 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/apps/variables.tf: -------------------------------------------------------------------------------- 1 | #Variables declaration 2 | 3 | #Authentication 4 | terraform { 5 | backend "azurerm" { 6 | } 7 | required_version = ">= 0.12.6" 8 | } 9 | 10 | variable "subscription_id" { 11 | description = "Azure subscription Id." 12 | type = string 13 | } 14 | 15 | variable "tenant_id" { 16 | description = "Azure tenant Id." 17 | type = string 18 | } 19 | 20 | variable "service_principals" { 21 | type = list 22 | description = "Azure service principals list containing the following keys : Application_Name, Application_Id, Application_Secret, Application_object_id." 23 | } 24 | 25 | #Common 26 | variable "app_name" { 27 | description = "Application name used in objects naming convention." 28 | type = string 29 | } 30 | 31 | variable "env_name" { 32 | description = "Environment name used in objects naming convention." 33 | type = string 34 | } 35 | 36 | variable "default_tags" { 37 | type = map(string) 38 | description = "Tag map that will be pushed on all Azure resources." 39 | } 40 | 41 | variable "rg_apps_name" { 42 | description = "Apps resource group name." 43 | type = string 44 | } 45 | 46 | variable "rg_infr_name" { 47 | description = "infra resource group name." 48 | type = string 49 | } 50 | 51 | variable "sa_infr_name" { 52 | description = "Infra storage account name." 53 | type = string 54 | } 55 | 56 | variable "bck_rsv_name" { 57 | description = "Infra recovery services vault name." 58 | type = string 59 | } 60 | 61 | #Subnet & Network Security group 62 | 63 | variable "apps_snets" { 64 | description = "Subnets properties." 65 | type = list(object({ 66 | vnet_name = string 67 | subnet_name = string 68 | })) 69 | } 70 | 71 | variable "apps_nsgs" { 72 | type = any 73 | description = "Apps Network Security Groups list containing the following keys : suffix_name." 74 | } 75 | 76 | #Load Balancers & Availability Set & Virtual Machines 77 | variable "linux_vms" { 78 | description = "Linux VMs list." 79 | type = any 80 | } 81 | 82 | variable "windows_vms" { 83 | description = "Windows VMs list." 84 | type = any 85 | } 86 | variable "Lb_sku" { 87 | description = "The SKU of the Azure Load Balancer. Accepted values are Basic and Standard. Defaults to Basic." 88 | type = string 89 | } 90 | 91 | variable "Lbs" { 92 | type = any 93 | description = "Load Balancer list containing the following keys : suffix_name, subnet_iteration, static_ip." 94 | } 95 | 96 | variable "LbRules" { 97 | type = any 98 | description = "Load Balancer rules list." 99 | } 100 | 101 | variable "app_admin" { 102 | description = "Specifies the name of the administrator account on the VM." 103 | type = string 104 | } 105 | 106 | variable "pass" { 107 | description = "Specifies the password of the administrator account on the VM." 108 | type = string 109 | } 110 | 111 | variable "ssh_key" { 112 | description = "Specifies the ssh public key to login on Linux VM." 113 | type = string 114 | } 115 | 116 | variable "key_vaults" { 117 | description = "List containing your key vaults." 118 | type = list(object({ 119 | suffix_name = string 120 | policy1_tenant_id = string 121 | policy1_object_id = string 122 | policy1_application_id = string 123 | })) 124 | } 125 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/infra/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | Step 1 : Deliver the Infra 4 | ----- 5 | The following sample will launch all the modules to show the reader how they are called. 6 | My advice is that the reader pick up the module he wants and calls it how it's shown in the root "main.tf" file. 7 | 8 | [Clone this repository](https://github.com/JamesDLD/terraform/tree/master/CreateAzureRm-Infra) 9 | 10 | With your Terraform template created, the first step is to initialize Terraform. 11 | This step ensures that Terraform has all the prerequisites to build your template in Azure. 12 | 13 | ```hcl 14 | 15 | terraform init -backend-config="variable/infra-backend-jdld.tfvars" -backend-config="../secret/backend-jdld.json" -reconfigure 16 | 17 | ``` 18 | 19 | The terraform plan command is used to create an execution plan. 20 | This step compares the requested resources to the state information saved by Terraform and then gives as an output the planned execution. Resources are not created in Azure. 21 | ```hcl 22 | 23 | terraform plan -var-file="variable/infra-main-jdld.tfvars" -var-file="../secret/main-jdld.tfvars" 24 | 25 | ``` 26 | 27 | If all is ok with the proposal you can now apply the configuration. 28 | ```hcl 29 | 30 | terraform apply -var-file="variable/infra-main-jdld.tfvars" -var-file="../secret/main-jdld.tfvars" 31 | 32 | ``` 33 | 34 | To destroy the associated resources. 35 | ```hcl 36 | 37 | terraform destroy -var-file="variable/infra-main-jdld.tfvars" -var-file="../secret/main-jdld.tfvars" 38 | 39 | ``` -------------------------------------------------------------------------------- /CreateAzureRm-Infra/infra/application_gateway.tf: -------------------------------------------------------------------------------- 1 | 2 | resource "azurerm_public_ip" "infra" { 3 | name = "appgateway1-pip1" 4 | resource_group_name = data.azurerm_resource_group.Infr.name 5 | location = data.azurerm_resource_group.Infr.location 6 | allocation_method = "Static" #Needed for app gw v2 7 | sku = "Standard" #Needed for app gw v2 8 | } 9 | 10 | # since these variables are re-used - a locals block makes this more maintainable 11 | locals { 12 | backend_address_pool_name = "infra-beap" 13 | frontend_port_name = "infra-feport" 14 | frontend_ip_configuration_name = "infra-feip" 15 | http_setting_name = "infra-be-htst" 16 | listener_name = "infra-httplstn" 17 | request_routing_rule_name = "infra-rqrt" 18 | redirect_configuration_name = "infra-rdrcfg" 19 | } 20 | 21 | resource "azurerm_application_gateway" "network" { 22 | name = "appgateway1" 23 | resource_group_name = data.azurerm_resource_group.Infr.name 24 | location = data.azurerm_resource_group.Infr.location 25 | 26 | sku { 27 | name = "Standard_v2" 28 | tier = "Standard_v2" 29 | capacity = 1 30 | } 31 | 32 | gateway_ip_configuration { 33 | name = "appgateway1-CFG" 34 | subnet_id = lookup(module.Az-VirtualNetwork-Infra.subnets, "ApplicationGatewatey1", null)["id"] 35 | } 36 | 37 | frontend_port { 38 | name = "${local.frontend_port_name}" 39 | port = 80 40 | } 41 | 42 | frontend_ip_configuration { 43 | name = "${local.frontend_ip_configuration_name}" 44 | public_ip_address_id = "${azurerm_public_ip.infra.id}" 45 | } 46 | 47 | backend_address_pool { 48 | name = "${local.backend_address_pool_name}" 49 | ip_addresses = ["10.0.2.8"] 50 | } 51 | 52 | backend_http_settings { 53 | name = "${local.http_setting_name}" 54 | cookie_based_affinity = "Disabled" 55 | path = "/" 56 | port = 80 57 | protocol = "Http" 58 | request_timeout = 1 59 | } 60 | 61 | http_listener { 62 | name = "${local.listener_name}" 63 | frontend_ip_configuration_name = "${local.frontend_ip_configuration_name}" 64 | frontend_port_name = "${local.frontend_port_name}" 65 | protocol = "Http" 66 | } 67 | 68 | request_routing_rule { 69 | name = "${local.request_routing_rule_name}" 70 | rule_type = "Basic" 71 | http_listener_name = "${local.listener_name}" 72 | backend_address_pool_name = "${local.backend_address_pool_name}" 73 | backend_http_settings_name = "${local.http_setting_name}" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/infra/variable/infra-backend-jdld.tfvars: -------------------------------------------------------------------------------- 1 | #Variables initialization 2 | storage_account_name = "infrasdbx1vpcjdld1" 3 | 4 | container_name = "tfstate" 5 | 6 | key = "infra.tfstate" 7 | 8 | resource_group_name = "infr-jdld-noprd-rg1" 9 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/infra/variables.tf: -------------------------------------------------------------------------------- 1 | #Variables declaration 2 | 3 | #Authentication 4 | terraform { 5 | backend "azurerm" { 6 | } 7 | required_version = ">= 0.12.6" 8 | } 9 | 10 | 11 | variable "subscription_id" { 12 | description = "Azure subscription Id." 13 | type = string 14 | } 15 | 16 | variable "tenant_id" { 17 | description = "Azure tenant Id." 18 | type = string 19 | } 20 | 21 | variable "service_principals" { 22 | description = "Azure service principals list." 23 | type = list(object({ 24 | Application_Name = string 25 | Application_Id = string 26 | Application_Secret = string 27 | Application_object_id = string 28 | })) 29 | } 30 | 31 | #Common 32 | variable "app_name" { 33 | description = "Application name used in objects naming convention." 34 | type = string 35 | } 36 | 37 | variable "env_name" { 38 | description = "Environment name used in objects naming convention." 39 | type = string 40 | } 41 | 42 | variable "default_tags" { 43 | type = map(string) 44 | description = "Tag map that will be pushed on all Azure resources." 45 | } 46 | 47 | variable "rg_apps_name" { 48 | description = "Apps resource group name." 49 | type = string 50 | } 51 | 52 | variable "rg_infr_name" { 53 | description = "infra resource group name." 54 | type = string 55 | } 56 | 57 | variable "sa_infr_name" { 58 | description = "Infra storage account name." 59 | type = string 60 | } 61 | 62 | variable "kv_sku" { 63 | description = "Key vault sku." 64 | type = string 65 | } 66 | 67 | variable "key_vaults" { 68 | description = "List containing your key vaults." 69 | type = list(object({ 70 | suffix_name = string 71 | policy1_tenant_id = string 72 | policy1_object_id = string 73 | policy1_application_id = string 74 | })) 75 | } 76 | 77 | variable "policies" { 78 | description = "Policies." 79 | type = list(object({ 80 | suffix_name = string #Used to name the policy and to call json template files located into the module's folder 81 | policy_type = string 82 | mode = string 83 | })) 84 | } 85 | 86 | variable "roles" { 87 | description = "Roles list." 88 | type = list(object({ 89 | suffix_name = string 90 | actions = string 91 | not_actions = string 92 | })) 93 | } 94 | 95 | #Backup 96 | variable "backup_policies" { 97 | description = "Recovery services vault backup policies." 98 | type = list(object({ 99 | Name = string 100 | scheduleRunFrequency = string 101 | scheduleRunDays = string 102 | scheduleRunTimes = string 103 | timeZone = string 104 | dailyRetentionDurationCount = number 105 | weeklyRetentionDurationCount = number 106 | monthlyRetentionDurationCount = number 107 | })) 108 | } 109 | 110 | #Azure Firewall 111 | variable "az_firewall_rules" { 112 | description = "Azure firewall rules." 113 | } 114 | 115 | 116 | #Vnet & Subnet & Network Security group 117 | variable "vnets" { 118 | description = "Virtual Networks list." 119 | type = any 120 | } 121 | 122 | variable "snets" { 123 | description = "Subnet list." 124 | type = any 125 | } 126 | 127 | variable "vnets_to_peer" { 128 | description = "Vnets to peer." 129 | type = any 130 | } 131 | 132 | variable "route_tables" { 133 | description = "Route table." 134 | type = any 135 | } 136 | 137 | variable "infra_nsgs" { 138 | type = any 139 | description = "Infra Network Security Groups list containing the following keys : suffix_name." 140 | } 141 | 142 | #Compute 143 | variable "app_admin" { 144 | description = "Specifies the name of the administrator account on the VM." 145 | type = string 146 | } 147 | 148 | variable "pass" { 149 | description = "Specifies the password of the administrator account on the VM." 150 | type = string 151 | } 152 | 153 | variable "ssh_key" { 154 | description = "Specifies the ssh public key to login on Linux VM." 155 | type = string 156 | } 157 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/secret/README.md: -------------------------------------------------------------------------------- 1 | Folder's golden rule 2 | ------------ 3 | - Store secrets 4 | - File containing password are ignored by any git push 5 | 6 | 7 | Secret files 8 | ------------ 9 | - backend-jdld.json : stores the credential to write Terraform backend files 10 | - Content sample ==> 11 | ```json 12 | { 13 | "tenant_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 14 | "subscription_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 15 | "client_id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 16 | "client_secret": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 17 | } 18 | ``` 19 | - main-jdld.tfvars : stores the credential to write in Infra and Apps Resource groups and store the VM credentials 20 | - Content sample ==> 21 | ```hcl 22 | subscription_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 23 | service_principals = [ 24 | { 25 | Application_Name = "sp_infra" #Subscription Owner & Read directory data on Windows Azure Active Directory 26 | Application_Id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 27 | Application_Secret = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 28 | Application_object_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #To get this param : $(Get-AzureRmADServicePrincipal -DisplayName $Application_Name).Id 29 | }, 30 | { 31 | Application_Name = "sp_apps" #No Privileges needed, will be set by the script 32 | Application_Id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 33 | Application_Secret = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 34 | Application_object_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #To get this param : $(Get-AzureRmADServicePrincipal -DisplayName $Application_Name).Id 35 | }, 36 | ] 37 | tenant_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 38 | 39 | #VM Credential and public key certificate 40 | app_admin = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 41 | pass = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 42 | ssh_key = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 43 | 44 | #Key Vault 45 | key_vaults = [ 46 | { 47 | suffix_name = "sci" 48 | policy1_tenant_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 49 | policy1_object_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 50 | policy1_application_id = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 51 | }, 52 | ] 53 | ``` -------------------------------------------------------------------------------- /CreateAzureRm-Infra/tools/bash_functions.sh: -------------------------------------------------------------------------------- 1 | #Color 2 | Correct='\033[0;32m' # Green 3 | Warning='\033[0;33m' # Jaune 4 | Error='\033[0;31m' # Red 5 | NoColor='\033[0m' 6 | 7 | function action_cmdlet { 8 | action="$1" 9 | cmdlet="$2" 10 | notanerror="$3" 11 | if [[ $notanerror = "" ]]; then notanerror="impossibletextthatyouwillneverfoundasanerror" ; fi 12 | 13 | echo -e "${Correct}$action...${NoColor}" 14 | action_cmdlet_output=$($cmdlet 2> /tmp/stderr_$$) 15 | if [[ $? != 0 ]]; then 16 | if [[ $(cat /tmp/stderr_$$) =~ $notanerror ]]; then 17 | echo -e "${Warning}You have decided that the following message is not an error : $(cat /tmp/stderr_$$) ${NoColor}" 18 | else 19 | echo -e "${Error}Error with action : $action...${NoColor}" 20 | echo -e "${Error}Error : $(cat /tmp/stderr_$$)...${NoColor}" 21 | exit 1 22 | fi 23 | fi 24 | return 0 25 | } 26 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/tools/mustache.min.js: -------------------------------------------------------------------------------- 1 | (function defineMustache(global, factory) { if (typeof exports === "object" && exports && typeof exports.nodeName !== "string") { factory(exports) } else if (typeof define === "function" && define.amd) { define(["exports"], factory) } else { global.Mustache = {}; factory(global.Mustache) } })(this, function mustacheFactory(mustache) { var objectToString = Object.prototype.toString; var isArray = Array.isArray || function isArrayPolyfill(object) { return objectToString.call(object) === "[object Array]" }; function isFunction(object) { return typeof object === "function" } function typeStr(obj) { return isArray(obj) ? "array" : typeof obj } function escapeRegExp(string) { return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&") } function hasProperty(obj, propName) { return obj != null && typeof obj === "object" && propName in obj } var regExpTest = RegExp.prototype.test; function testRegExp(re, string) { return regExpTest.call(re, string) } var nonSpaceRe = /\S/; function isWhitespace(string) { return !testRegExp(nonSpaceRe, string) } var entityMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/", "`": "`", "=": "=" }; function escapeHtml(string) { return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap(s) { return entityMap[s] }) } var whiteRe = /\s*/; var spaceRe = /\s+/; var equalsRe = /\s*=/; var curlyRe = /\s*\}/; var tagRe = /#|\^|\/|>|\{|&|=|!/; function parseTemplate(template, tags) { if (!template) return []; var sections = []; var tokens = []; var spaces = []; var hasTag = false; var nonSpace = false; function stripSpace() { if (hasTag && !nonSpace) { while (spaces.length) delete tokens[spaces.pop()] } else { spaces = [] } hasTag = false; nonSpace = false } var openingTagRe, closingTagRe, closingCurlyRe; function compileTags(tagsToCompile) { if (typeof tagsToCompile === "string") tagsToCompile = tagsToCompile.split(spaceRe, 2); if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) throw new Error("Invalid tags: " + tagsToCompile); openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + "\\s*"); closingTagRe = new RegExp("\\s*" + escapeRegExp(tagsToCompile[1])); closingCurlyRe = new RegExp("\\s*" + escapeRegExp("}" + tagsToCompile[1])) } compileTags(tags || mustache.tags); var scanner = new Scanner(template); var start, type, value, chr, token, openSection; while (!scanner.eos()) { start = scanner.pos; value = scanner.scanUntil(openingTagRe); if (value) { for (var i = 0, valueLength = value.length; i < valueLength; ++i) { chr = value.charAt(i); if (isWhitespace(chr)) { spaces.push(tokens.length) } else { nonSpace = true } tokens.push(["text", chr, start, start + 1]); start += 1; if (chr === "\n") stripSpace() } } if (!scanner.scan(openingTagRe)) break; hasTag = true; type = scanner.scan(tagRe) || "name"; scanner.scan(whiteRe); if (type === "=") { value = scanner.scanUntil(equalsRe); scanner.scan(equalsRe); scanner.scanUntil(closingTagRe) } else if (type === "{") { value = scanner.scanUntil(closingCurlyRe); scanner.scan(curlyRe); scanner.scanUntil(closingTagRe); type = "&" } else { value = scanner.scanUntil(closingTagRe) } if (!scanner.scan(closingTagRe)) throw new Error("Unclosed tag at " + scanner.pos); token = [type, value, start, scanner.pos]; tokens.push(token); if (type === "#" || type === "^") { sections.push(token) } else if (type === "/") { openSection = sections.pop(); if (!openSection) throw new Error('Unopened section "' + value + '" at ' + start); if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start) } else if (type === "name" || type === "{" || type === "&") { nonSpace = true } else if (type === "=") { compileTags(value) } } openSection = sections.pop(); if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); return nestTokens(squashTokens(tokens)) } function squashTokens(tokens) { var squashedTokens = []; var token, lastToken; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { token = tokens[i]; if (token) { if (token[0] === "text" && lastToken && lastToken[0] === "text") { lastToken[1] += token[1]; lastToken[3] = token[3] } else { squashedTokens.push(token); lastToken = token } } } return squashedTokens } function nestTokens(tokens) { var nestedTokens = []; var collector = nestedTokens; var sections = []; var token, section; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { token = tokens[i]; switch (token[0]) { case "#": case "^": collector.push(token); sections.push(token); collector = token[4] = []; break; case "/": section = sections.pop(); section[5] = token[2]; collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens; break; default: collector.push(token) } } return nestedTokens } function Scanner(string) { this.string = string; this.tail = string; this.pos = 0 } Scanner.prototype.eos = function eos() { return this.tail === "" }; Scanner.prototype.scan = function scan(re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ""; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string }; Scanner.prototype.scanUntil = function scanUntil(re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ""; break; case 0: match = ""; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index) }this.pos += match.length; return match }; function Context(view, parentContext) { this.view = view; this.cache = { ".": this.view }; this.parent = parentContext } Context.prototype.push = function push(view) { return new Context(view, this) }; Context.prototype.lookup = function lookup(name) { var cache = this.cache; var value; if (cache.hasOwnProperty(name)) { value = cache[name] } else { var context = this, names, index, lookupHit = false; while (context) { if (name.indexOf(".") > 0) { value = context.view; names = name.split("."); index = 0; while (value != null && index < names.length) { if (index === names.length - 1) lookupHit = hasProperty(value, names[index]); value = value[names[index++]] } } else { value = context.view[name]; lookupHit = hasProperty(context.view, name) } if (lookupHit) break; context = context.parent } cache[name] = value } if (isFunction(value)) value = value.call(this.view); return value }; function Writer() { this.cache = {} } Writer.prototype.clearCache = function clearCache() { this.cache = {} }; Writer.prototype.parse = function parse(template, tags) { var cache = this.cache; var tokens = cache[template]; if (tokens == null) tokens = cache[template] = parseTemplate(template, tags); return tokens }; Writer.prototype.render = function render(template, view, partials) { var tokens = this.parse(template); var context = view instanceof Context ? view : new Context(view); return this.renderTokens(tokens, context, partials, template) }; Writer.prototype.renderTokens = function renderTokens(tokens, context, partials, originalTemplate) { var buffer = ""; var token, symbol, value; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { value = undefined; token = tokens[i]; symbol = token[0]; if (symbol === "#") value = this.renderSection(token, context, partials, originalTemplate); else if (symbol === "^") value = this.renderInverted(token, context, partials, originalTemplate); else if (symbol === ">") value = this.renderPartial(token, context, partials, originalTemplate); else if (symbol === "&") value = this.unescapedValue(token, context); else if (symbol === "name") value = this.escapedValue(token, context); else if (symbol === "text") value = this.rawValue(token); if (value !== undefined) buffer += value } return buffer }; Writer.prototype.renderSection = function renderSection(token, context, partials, originalTemplate) { var self = this; var buffer = ""; var value = context.lookup(token[1]); function subRender(template) { return self.render(template, context, partials) } if (!value) return; if (isArray(value)) { for (var j = 0, valueLength = value.length; j < valueLength; ++j) { buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate) } } else if (typeof value === "object" || typeof value === "string" || typeof value === "number") { buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate) } else if (isFunction(value)) { if (typeof originalTemplate !== "string") throw new Error("Cannot use higher-order sections without the original template"); value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender); if (value != null) buffer += value } else { buffer += this.renderTokens(token[4], context, partials, originalTemplate) } return buffer }; Writer.prototype.renderInverted = function renderInverted(token, context, partials, originalTemplate) { var value = context.lookup(token[1]); if (!value || isArray(value) && value.length === 0) return this.renderTokens(token[4], context, partials, originalTemplate) }; Writer.prototype.renderPartial = function renderPartial(token, context, partials) { if (!partials) return; var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; if (value != null) return this.renderTokens(this.parse(value), context, partials, value) }; Writer.prototype.unescapedValue = function unescapedValue(token, context) { var value = context.lookup(token[1]); if (value != null) return value }; Writer.prototype.escapedValue = function escapedValue(token, context) { var value = context.lookup(token[1]); if (value != null) return mustache.escape(value) }; Writer.prototype.rawValue = function rawValue(token) { return token[1] }; mustache.name = "mustache.js"; mustache.version = "2.3.0"; mustache.tags = ["{{", "}}"]; var defaultWriter = new Writer; mustache.clearCache = function clearCache() { return defaultWriter.clearCache() }; mustache.parse = function parse(template, tags) { return defaultWriter.parse(template, tags) }; mustache.render = function render(template, view, partials) { if (typeof template !== "string") { throw new TypeError('Invalid template! Template should be a "string" ' + 'but "' + typeStr(template) + '" was given as the first ' + "argument for mustache#render(template, view, partials)") } return defaultWriter.render(template, view, partials) }; mustache.to_html = function to_html(template, view, partials, send) { var result = mustache.render(template, view, partials); if (isFunction(send)) { send(result) } else { return result } }; mustache.escape = escapeHtml; mustache.Scanner = Scanner; mustache.Context = Context; mustache.Writer = Writer; return mustache }); -------------------------------------------------------------------------------- /CreateAzureRm-Infra/tools/process_template.handlebar.js: -------------------------------------------------------------------------------- 1 | //const mustache = require('./mustache.min.js'); 2 | const mustache = require('./handlebars'); 3 | const fs = require('fs'); 4 | 5 | let args = process.argv.slice(2); 6 | 7 | let template_file = args[0]; 8 | let json_ressource_file = args[1]; 9 | let dest = args[2]; 10 | 11 | if (!dest) { 12 | console.log('node process_template.js template_file ressource_file dest_file'); 13 | return; 14 | } 15 | 16 | 17 | if (!fs.existsSync(template_file)) { 18 | console.error('file ' + template_file + ' does not exist'); 19 | return; 20 | } 21 | 22 | if (!fs.existsSync(json_ressource_file)) { 23 | console.error('file ' + json_ressource_file + ' does not exist'); 24 | return; 25 | } 26 | 27 | 28 | 29 | 30 | let template = fs.readFileSync(template_file, { encoding: "utf8" }); 31 | let json_ressource = JSON.parse(fs.readFileSync(json_ressource_file, { encoding: "utf8" })); 32 | 33 | Object.assign(json_ressource, { template_warning: "Autogenerated file, see mustache folder if you want to overwrite this file" }); 34 | 35 | let handletemplate = mustache.compile(template); 36 | let res = handletemplate(json_ressource); 37 | 38 | fs.writeFileSync(dest, res, { encoding: "utf8" }); 39 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/tools/process_template.js: -------------------------------------------------------------------------------- 1 | const mustache = require('./mustache.min.js'); 2 | const fs = require('fs'); 3 | 4 | let args = process.argv.slice(2); 5 | 6 | let template_file = args[0]; 7 | let json_ressource_file = args[1]; 8 | let dest = args[2]; 9 | 10 | if (!dest) { 11 | console.log('node process_template.js template_file ressource_file dest_file'); 12 | return; 13 | } 14 | 15 | 16 | if (!fs.existsSync(template_file)) { 17 | console.error('file ' + template_file + ' does not exist'); 18 | return; 19 | } 20 | 21 | if (!fs.existsSync(json_ressource_file)) { 22 | console.error('file ' + json_ressource_file + ' does not exist'); 23 | return; 24 | } 25 | 26 | 27 | 28 | 29 | let template = fs.readFileSync(template_file, { encoding: "utf8" }); 30 | let json_ressource = JSON.parse(fs.readFileSync(json_ressource_file, { encoding: "utf8" })); 31 | 32 | Object.assign(json_ressource, { template_warning: "Autogenerated file, see mustache folder if you want to overwrite this file" }); 33 | 34 | let res = mustache.render(template, json_ressource); 35 | 36 | fs.writeFileSync(dest, res, { encoding: "utf8" }); 37 | -------------------------------------------------------------------------------- /CreateAzureRm-Infra/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesDLD/terraform/0c71dce4e1814ff2cf5fbeaa99fe0f6e983db9fb/CreateAzureRm-Infra/workflow.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Repository inventory 2 | ------------ 3 | 4 | | Id | Description | Build Status | 5 | | ------------- | ------------- | ------------- | 6 | | [Application Gateway](azurerm_application_gateway) | Tutoriel on [medium.com](https://medium.com/faun/build-an-azure-application-gateway-with-terraform-8264fbd5fa42/?WT.mc_id=AZ-MVP-5003548) on how to build an Azure Application Gateway | N/A | 7 | | [Best-Practice](Best-Practice) | Share a list of best practices and tutoriels when using Terraform on Azure | [![Build Status](https://dev.azure.com/jamesdld23/vpc_lab/_apis/build/status/JamesDLD.terraform%20BP?branchName=master)](https://dev.azure.com/jamesdld23/vpc_lab/_build/latest?definitionId=5&branchName=master) | 8 | | [Azure DevOps - Intro](AzureDevops-Introduction) | Share articles about CI/CD, Azure DevOps and Terraform on Azure. | [![Build Status](https://dev.azure.com/jamesdld23/vpc_lab/_apis/build/status/JamesDLD.terraform%20Introduction?branchName=master)](https://dev.azure.com/jamesdld23/vpc_lab/_build/latest?definitionId=9&branchName=master) | 9 | | [CreateAzureRm-Infra](CreateAzureRm-Infra) | Share Terraform script that reveal how to create a VPC in Azure and how application client can create their resources | [![Build Status](https://dev.azure.com/jamesdld23/vpc_lab/_apis/build/status/JamesDLD.terraform%20VPC?branchName=master)](https://dev.azure.com/jamesdld23/vpc_lab/_build/latest?definitionId=6&branchName=master) | 10 | 11 | 12 | Azure and Terraform 13 | ------------ 14 | Simple and Powerful 15 | 16 | HashiCorp Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared among team members, treated as code, edited, reviewed, and versioned. 17 | 18 | The following table is a quick comparison feedback between Terraform and Azure ARM template. 19 | 20 | | Comparison | Terraform | ARM Template | 21 | | ------------- | ------------- | ------------- | 22 | | Pro | Common language to deal with several providers (Azure including AzureRm and Azure AD, AWS, Nutanix, VMware, Docker,...)

Detect if a resource's parameter could be updated in place or if the resources need to be re created

Compliant test could be done easily to ensure that what you have deployed remains coherent

Facilitating CICD testing as the "plan" function tells you exactly what need to be done

If the Terraform resource doesn't exist we can execute ARM template from the Terraform resource "azurerm_template_deployment" | Microsoft Azure ownership

Detect if a resource's parameter could be updated in place or if the resources need to be re created through the [What-if](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-deploy-what-if?tabs=azure-powershell) feature

Compliant test could be done easily to ensure that what you have deployed remains coherent through the [What-if](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-deploy-what-if?tabs=azure-powershell) feature

Facilitating CICD testing as the the [What-if](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-deploy-what-if?tabs=azure-powershell) feature tells you exactly what need to be done

If the ARM template could not be used, we can launch a [Deployment Scripts](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-script-template?tabs=CLI) from an ARM template

Variety of parameters types

Deployment log stored in the Azure Resource Group | 23 | | Cons | Could not use secure object as parameter

New release might not be delivered as fast if it was the provider own tool | AzureRm only, we can mitigate this cons using [Deployment Scripts](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-script-template?tabs=CLI)

The deployment mode "complete" permits to guarantee that your RG contains exactly what you want but the ARM template could be hard to read depending on the number of resources you put on it | 24 | 25 | 26 | About the [Terraform's modules](https://registry.terraform.io/modules/JamesDLD) 27 | ------------ 28 | On of the objective here is to share Terraform custom modules with the community with the following guidelines : 29 | - a module is used when we need to call a given number of resources several times and the same way, for exemple : when creating a VM we need nic, disks, backup, log monitoring, etc .. 30 | - a module doesn't contain any static values 31 | - a module is called using variables 32 | 33 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | google_analytics: UA-129401405-1 3 | -------------------------------------------------------------------------------- /_includes/google-analytics.html: -------------------------------------------------------------------------------- 1 | ga('create', '{{ site.google_analytics }}', 'auto'); -------------------------------------------------------------------------------- /azurerm_application_gateway/README.md: -------------------------------------------------------------------------------- 1 | [Previous page >](../) 2 | 3 | Content 4 | ------------ 5 | 6 | Tutoriel on [medium.com](https://medium.com/faun/build-an-azure-application-gateway-with-terraform-8264fbd5fa42/?WT.mc_id=AZ-MVP-5003548) on how to build an Azure Application Gateway with: 7 | - A Monitoring Dashboard hosted on a [Log Analytics Workspace](https://docs.microsoft.com/en-us/azure/azure-monitor/insights/azure-networking-analytics?WT.mc_id=AZ-MVP-5003548). 8 | - A [Key Vault](https://docs.microsoft.com/en-us/azure/application-gateway/key-vault-certs?WT.mc_id=AZ-MVP-5003548) as a safeguard of our Web TLS/SSL certificates. 9 | 10 | 11 | Learn how to Use Terraform to build an Azure Application Gateway with: 12 | - a Monitoring Dashboard hosted on a Log Analytics Workspace, 13 | - and a Key Vault as a safeguard of Web TLS/SSL certificates. 14 | #Azure #ApplicationGateway #Terraform #KeyVault #LogMonitor 15 | 16 | Authentication 17 | ------------ 18 | Currently we use Authentication through AZ CLI : https://www.terraform.io/docs/providers/azurerm/guides/azure_cli.html 19 | ``` 20 | az login 21 | az account set --subscription="mvp-sub1" 22 | ``` 23 | 24 | 25 | Sample usage on Net 26 | ----- 27 | 28 | This step ensures that Terraform has all the prerequisites to build your template in Azure. 29 | ```hcl 30 | 31 | terraform init 32 | 33 | ``` 34 | 35 | The terraform plan command is used to create an execution plan. 36 | This step compares the requested resources to the state information saved by Terraform and then gives as an output the planned execution. Resources are not created in Azure. 37 | ```hcl 38 | terraform plan 39 | ``` 40 | 41 | If all is ok with the proposal you can now apply the configuration. 42 | ```hcl 43 | terraform apply 44 | ``` 45 | -------------------------------------------------------------------------------- /azurerm_application_gateway/provider.tf: -------------------------------------------------------------------------------- 1 | #Set the terraform backend 2 | terraform { 3 | backend "azurerm" { 4 | resource_group_name = "infr-jdld-noprd-rg1" 5 | storage_account_name = "infrasdbx1vpcjdld1" #Name must be unique 6 | container_name = "tfstate" 7 | key = "appgw.tfstate" 8 | } 9 | #required_version = "0.13.3" 10 | } 11 | 12 | #Set the Provider 13 | provider "azurerm" { 14 | #version = "2.28.0" 15 | skip_provider_registration = true #(Optional) Should the AzureRM Provider skip registering the Resource Providers it supports? This can also be sourced from the ARM_SKIP_PROVIDER_REGISTRATION Environment Variable. Defaults to false. 16 | features {} 17 | 18 | #subscription_id = var.subscription_id 19 | # client_id = var.client_id 20 | # client_secret = var.client_secret 21 | # tenant_id = var.tenant_id 22 | 23 | } 24 | -------------------------------------------------------------------------------- /module/Add-AzureRmLoadBalancerOutboundRules/AzureRmLoadBalancerOutboundRules_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2018-05-01/deploymentTemplate.json", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "apiVersion": { 6 | "defaultValue": "2018-08-01", 7 | "type": "string", 8 | "metadata": { 9 | "description": "API version 2018-08-01 permits an outbound rule definition structured : https://docs.microsoft.com/en-us/azure/load-balancer/load-balancer-outbound-rules-overview" 10 | } 11 | }, 12 | "lbName": { 13 | "type": "string", 14 | "metadata": { 15 | "description": "Load balancer name." 16 | } 17 | }, 18 | "tags": { 19 | "type": "string", 20 | "defaultValue": "{\"APP\":\"XXX\",\"BUD\":\"XXX\",\"CTC\":\"XXX@XXX\",\"ENV\":\"XXX\"}", 21 | "metadata": { 22 | "description": "Tags of the Vault" 23 | } 24 | }, 25 | "sku": { 26 | "type": "string", 27 | "metadata": { 28 | "description": "Load balancer and public IP sku." 29 | }, 30 | "allowedValues": [ 31 | "Standard" 32 | ] 33 | }, 34 | "publicIPAllocationMethod": { 35 | "type": "string", 36 | "defaultValue": "Static", 37 | "metadata": { 38 | "description": "The public IP allocation method. Possible values are: 'Static' and 'Dynamic'. - Static or Dynamic." 39 | } 40 | }, 41 | "allocatedOutboundPorts": { 42 | "type": "string", 43 | "metadata": { 44 | "description": "Number of SNAT ports, Load Balancer allocates SNAT ports in multiples of 8." 45 | } 46 | }, 47 | "idleTimeoutInMinutes": { 48 | "type": "string", 49 | "metadata": { 50 | "description": "Outbound flow idle timeout. The parameter accepts a value from 4 to 66." 51 | } 52 | }, 53 | "enableTcpReset": { 54 | "type": "string", 55 | "defaultValue": "false", 56 | "metadata": { 57 | "description": "Enable TCP Reset on idle timeout." 58 | } 59 | }, 60 | "protocol": { 61 | "type": "string", 62 | "metadata": { 63 | "description": "Transport protocol of the outbound rule." 64 | }, 65 | "allowedValues": [ 66 | "Tcp", 67 | "Udp", 68 | "All" 69 | ] 70 | }, 71 | "lb_public_ip_id": { 72 | "type": "string", 73 | "defaultValue": "", 74 | "metadata": { 75 | "description": "Id of an existing public ip." 76 | } 77 | } 78 | }, 79 | "variables": { 80 | "enableTcpReset": "[contains(parameters('enableTcpReset'), 'true')]", 81 | "publicIPAddressName": "[concat(parameters('lbName'), '_pip1')]", 82 | "outboundRuleName": "[concat(parameters('lbName'), '_outrule1')]", 83 | "frontend_ip_configuration_name": "[concat(parameters('lbName'), '_lbcfg1')]", 84 | "backendAddressPoolName": "[concat(parameters('lbName'), '_bckpool1')]", 85 | "lbID": "[resourceId('Microsoft.Network/loadBalancers',parameters('lbName'))]", 86 | "publicIPAddressID": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]", 87 | "condition_publicIPAddressID": "[if(empty(parameters('lb_public_ip_id')), variables('publicIPAddressID'), parameters('lb_public_ip_id'))]", 88 | "outboundRuleID": "[concat(variables('lbID'), '/outboundRules/', variables('outboundRuleName'))]", 89 | "frontendIPConfigurationID": "[concat(variables('lbID'), '/frontendIPConfigurations/', variables('frontend_ip_configuration_name'))]", 90 | "backendAddressPoolId": "[concat(variables('lbID'),'/backendAddressPools/', variables('backendAddressPoolName'))]", 91 | "tagsbase64": "[base64(parameters('tags'))]" 92 | }, 93 | "resources": [ 94 | { 95 | "apiVersion": "2018-08-01", 96 | "type": "Microsoft.Network/publicIPAddresses", 97 | "name": "[variables('publicIPAddressName')]", 98 | "location": "[resourceGroup().location]", 99 | "condition": "[empty(parameters('lb_public_ip_id'))]", 100 | "tags": "[base64ToJson(variables('tagsbase64'))]", 101 | "sku": { 102 | "name": "[parameters('sku')]" 103 | }, 104 | "properties": { 105 | "publicIPAllocationMethod": "[parameters('publicIPAllocationMethod')]" 106 | } 107 | }, 108 | { 109 | "apiVersion": "[parameters('apiVersion')]", 110 | "name": "[parameters('lbName')]", 111 | "type": "Microsoft.Network/loadBalancers", 112 | "location": "[resourceGroup().location]", 113 | "tags": "[base64ToJson(variables('tagsbase64'))]", 114 | "sku": { 115 | "name": "[parameters('sku')]" 116 | }, 117 | "dependsOn": [ 118 | "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]" 119 | ], 120 | "properties": { 121 | "frontendIPConfigurations": [ 122 | { 123 | "name": "[variables('frontend_ip_configuration_name')]", 124 | "properties": { 125 | "publicIPAddress": { 126 | "id": "[variables('condition_publicIPAddressID')]" 127 | } 128 | } 129 | } 130 | ], 131 | "backendAddressPools": [ 132 | { 133 | "name": "[variables('backendAddressPoolName')]" 134 | } 135 | ], 136 | "outboundRules": [ 137 | { 138 | "name": "[variables('outboundRuleName')]", 139 | "id": "[variables('outboundRuleID')]", 140 | "properties": { 141 | "frontendIPConfigurations": [ 142 | { 143 | "id": "[variables('frontendIPConfigurationID')]" 144 | } 145 | ], 146 | "allocatedOutboundPorts": "[int(parameters('allocatedOutboundPorts'))]", 147 | "idleTimeoutInMinutes": "[int(parameters('idleTimeoutInMinutes'))]", 148 | "enableTcpReset": "[variables('enableTcpReset')]", 149 | "protocol": "[parameters('protocol')]", 150 | "backendAddressPool": { 151 | "id": "[variables('backendAddressPoolId')]" 152 | } 153 | } 154 | } 155 | ] 156 | } 157 | } 158 | ], 159 | "outputs": { 160 | "load_balancer_backend_address_pools_id": { 161 | "type": "string", 162 | "value": "[variables('backendAddressPoolId')]" 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /module/Add-AzureRmLoadBalancerOutboundRules/README.md: -------------------------------------------------------------------------------- 1 | Usage 2 | ----- 3 | ```hcl 4 | #Set the Provider 5 | provider "azurerm" { 6 | tenant_id = var.tenant_id 7 | subscription_id = var.subscription_id 8 | client_id = var.client_id 9 | client_secret = var.client_secret 10 | version = "~> 2.0" 11 | features {} 12 | } 13 | 14 | #Set authentication variables 15 | variable "tenant_id" { 16 | description = "Azure tenant Id." 17 | } 18 | 19 | variable "subscription_id" { 20 | description = "Azure subscription Id." 21 | } 22 | 23 | variable "client_id" { 24 | description = "Azure service principal application Id." 25 | } 26 | 27 | variable "client_secret" { 28 | description = "Azure service principal application Secret." 29 | } 30 | 31 | #Set resource variables 32 | 33 | variable "lbs_public" { 34 | default = [ 35 | { 36 | suffix_name = "testpublic" 37 | sku = "Standard" 38 | allocatedOutboundPorts = "1000" #Number of SNAT ports, Load Balancer allocates SNAT ports in multiples of 8. 39 | idleTimeoutInMinutes = "4" #Outbound flow idle timeout. The parameter accepts a value from 4 to 66. 40 | enableTcpReset = "false" #Enable TCP Reset on idle timeout. 41 | protocol = "All" #Transport protocol of the outbound rule. 42 | }, 43 | ] 44 | } 45 | 46 | variable "default_tags" { 47 | default = { 48 | ENV = "sand1" 49 | APP = "JDLD" 50 | BUD = "FR_BXXXXX" 51 | CTC = "j.dumont@veebaze.com" 52 | } 53 | } 54 | 55 | #Call module 56 | module "Add-AzureRmLoadBalancerOutboundRules-Demo" { 57 | source = "github.com/JamesDLD/terraform/module/Add-AzureRmLoadBalancerOutboundRules" 58 | lbs_out = var.lbs_public 59 | lb_out_prefix = "myapp-demo-" 60 | lb_out_suffix = "-publiclb1" 61 | lb_out_resource_group_name = "infr-jdld-noprd-rg1" 62 | lbs_tags = var.default_tags 63 | #Optional : you can use an existing public ip, otherwise it will create one 64 | #lb_public_ip_id = "/subscriptions/mysubid/resourceGroups/tenant-testi-prd-rg/providers/Microsoft.Network/publicIPAddresses/scinfra-testi-prd-pip-0" 65 | } 66 | 67 | ``` -------------------------------------------------------------------------------- /module/Add-AzureRmLoadBalancerOutboundRules/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_template_deployment" "lb_to_addOutboundRule" { 2 | name = "${var.lbs_out[0]["suffix_name"]}-bck-DEP" 3 | resource_group_name = var.lb_out_resource_group_name 4 | template_body = file( 5 | "${path.module}/AzureRmLoadBalancerOutboundRules_template.json", 6 | ) 7 | deployment_mode = "Incremental" 8 | 9 | parameters = { 10 | lbName = "${var.lb_out_prefix}${var.lbs_out[0]["suffix_name"]}${var.lb_out_suffix}" 11 | tags = jsonencode(var.lbs_tags) 12 | sku = var.lbs_out[0]["sku"] 13 | allocatedOutboundPorts = var.lbs_out[0]["allocatedOutboundPorts"] 14 | idleTimeoutInMinutes = var.lbs_out[0]["idleTimeoutInMinutes"] 15 | enableTcpReset = var.lbs_out[0]["enableTcpReset"] 16 | protocol = var.lbs_out[0]["protocol"] 17 | lb_public_ip_id = var.lb_public_ip_id 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /module/Add-AzureRmLoadBalancerOutboundRules/output.tf: -------------------------------------------------------------------------------- 1 | output "lb_to_addOutboundRule_deployment_name" { 2 | value = azurerm_template_deployment.lb_to_addOutboundRule.name 3 | } 4 | 5 | output "lb_backend_id" { 6 | value = azurerm_template_deployment.lb_to_addOutboundRule.outputs["load_balancer_backend_address_pools_id"] 7 | } 8 | 9 | -------------------------------------------------------------------------------- /module/Add-AzureRmLoadBalancerOutboundRules/var.tf: -------------------------------------------------------------------------------- 1 | variable "lbs_out" { 2 | description = "Load balancer properties." 3 | type = list(object({ 4 | suffix_name = string 5 | sku = string 6 | allocatedOutboundPorts = number #Number of SNAT ports, Load Balancer allocates SNAT ports in multiples of 8. 7 | idleTimeoutInMinutes = number #Outbound flow idle timeout. The parameter accepts a value from 4 to 66. 8 | enableTcpReset = bool #Enable TCP Reset on idle timeout. 9 | protocol = string #Transport protocol of the outbound rule. 10 | })) 11 | } 12 | 13 | variable "lb_out_prefix" { 14 | type=string 15 | description = "Prefix used to set a common naming convention on the lb objects." 16 | } 17 | 18 | variable "lb_out_suffix" { 19 | type=string 20 | description = "Prefix used to set a common naming convention on the lb objects." 21 | } 22 | 23 | variable "lb_out_resource_group_name" { 24 | type=string 25 | description = "Load balancer resource group name." 26 | } 27 | 28 | variable "lbs_tags" { 29 | type = map(string) 30 | description = "Load balancer tags." 31 | } 32 | 33 | variable "lb_public_ip_id" { 34 | type=string 35 | description = "Id of an existing public ip" 36 | default = "" 37 | } 38 | 39 | -------------------------------------------------------------------------------- /module/Add-AzureRmLoadBalancerOutboundRules/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12" 3 | } 4 | -------------------------------------------------------------------------------- /module/Az-Bastion/AzureRmBastion_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "location": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Location of the Bastion." 9 | } 10 | }, 11 | "resourceGroupName": { 12 | "type": "string", 13 | "metadata": { 14 | "description": "Resource group name of the Bastion." 15 | } 16 | }, 17 | "bastionHostName": { 18 | "type": "string", 19 | "metadata": { 20 | "description": "Name of the Bastion." 21 | } 22 | }, 23 | "subnetName": { 24 | "type": "string", 25 | "metadata": { 26 | "description": "Subnet name of the Bastion." 27 | }, 28 | "defaultValue": "AzureBastionSubnet" 29 | }, 30 | "publicIpAddressName": { 31 | "type": "string", 32 | "metadata": { 33 | "description": "Public IP address name." 34 | } 35 | }, 36 | "existingVNETName": { 37 | "type": "string", 38 | "metadata": { 39 | "description": "Vnet name of the Bastion." 40 | } 41 | }, 42 | "subnetAddressPrefix": { 43 | "type": "string", 44 | "metadata": { 45 | "description": "Subnet prefix that is dedicated to the Bastion." 46 | } 47 | }, 48 | "tags": { 49 | "type": "string", 50 | "defaultValue": "{\"APP\":\"XXX\",\"BUD\":\"XXX\",\"CTC\":\"XXX@XXX\",\"ENV\":\"XXX\"}", 51 | "metadata": { 52 | "description": "Tags of the Bastion and it's public IP." 53 | } 54 | } 55 | }, 56 | "variables": { 57 | "tagsbase64": "[base64(parameters('tags'))]" 58 | }, 59 | "resources": [ 60 | { 61 | "apiVersion": "2019-02-01", 62 | "type": "Microsoft.Network/publicIpAddresses", 63 | "name": "[parameters('publicIpAddressName')]", 64 | "location": "[parameters('location')]", 65 | "sku": { 66 | "name": "Standard" 67 | }, 68 | "properties": { 69 | "publicIPAllocationMethod": "Static" 70 | }, 71 | "tags": "[base64ToJson(variables('tagsbase64'))]" 72 | }, 73 | { 74 | "apiVersion": "2018-04-01", 75 | "type": "Microsoft.Network/virtualNetworks/subnets", 76 | "name": "[concat(parameters('existingVNETName'), '/', parameters('subnetName'))]", 77 | "location": "[parameters('location')]", 78 | "properties": { 79 | "addressPrefix": "[parameters('subnetAddressPrefix')]" 80 | } 81 | }, 82 | { 83 | "apiVersion": "2018-10-01", 84 | "type": "Microsoft.Network/bastionHosts", 85 | "name": "[parameters('bastionHostName')]", 86 | "location": "[parameters('location')]", 87 | "dependsOn": [ 88 | "[resourceId(parameters('resourceGroupName'), 'Microsoft.Network/publicIpAddresses', parameters('publicIpAddressName'))]" 89 | ], 90 | "properties": { 91 | "ipConfigurations": [ 92 | { 93 | "name": "[concat(parameters('bastionHostName'),'-CFG')]", 94 | "properties": { 95 | "subnet": { 96 | "id": "[resourceId(parameters('resourceGroupName'), 'Microsoft.Network/virtualNetworks/subnets', parameters('existingVNETName'),parameters('subnetName'))]" 97 | }, 98 | "publicIPAddress": { 99 | "id": "[resourceId(parameters('resourceGroupName'), 'Microsoft.Network/publicIpAddresses', parameters('publicIpAddressName'))]" 100 | } 101 | } 102 | } 103 | ] 104 | }, 105 | "tags": "[base64ToJson(variables('tagsbase64'))]" 106 | } 107 | ], 108 | "outputs": { 109 | "resourceID": { 110 | "type": "string", 111 | "value": "[resourceId('Microsoft.Network/bastionHosts', parameters('bastionHostName'))]" 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /module/Az-Bastion/README.md: -------------------------------------------------------------------------------- 1 | 2 | Prerequisite 3 | ----- 4 | You should register to the preview to be able to create an Azure Bastion. Using PowerShell : 5 | ``` 6 | Login-AzAccount 7 | Register-AzProviderFeature -FeatureName AllowBastionHost -ProviderNamespace Microsoft.Network 8 | Register-AzResourceProvider -ProviderNamespace Microsoft.Network 9 | ``` 10 | 11 | Wait for few minutes then use the following cmdlet to ensure that you are registred : 12 | ``` 13 | Get-AzProviderFeature -ListAvailable 14 | ``` 15 | 16 | Usage 17 | ----- 18 | ``` 19 | #Set the Provider 20 | provider "azurerm" { 21 | tenant_id = var.tenant_id 22 | subscription_id = var.subscription_id 23 | client_id = var.client_id 24 | client_secret = var.client_secret 25 | version = "~> 2.0" 26 | features {} 27 | } 28 | 29 | #Set authentication variables 30 | variable "tenant_id" { 31 | description = "Azure tenant Id." 32 | } 33 | 34 | variable "subscription_id" { 35 | description = "Azure subscription Id." 36 | } 37 | 38 | variable "client_id" { 39 | description = "Azure service principal application Id." 40 | } 41 | 42 | variable "client_secret" { 43 | description = "Azure service principal application Secret." 44 | } 45 | 46 | #Set resource variables 47 | 48 | variable "default_tags" { 49 | default = { 50 | ENV = "sand1" 51 | APP = "JDLD" 52 | BUD = "FR_BXXXXX" 53 | CTC = "j.dumont@veebaze.com" 54 | } 55 | } 56 | 57 | #Call module 58 | module "Az-Bastion-Demo" { 59 | source = "github.com/JamesDLD/terraform/module/Az-Bastion" 60 | location = "westeurope" 61 | resourceGroupName = "infr-jdld-noprd-rg2" 62 | bastionHostName = "apps-bas1" 63 | existingVNETName = "infra-jdld-infr-apps-net1" 64 | publicIpAddressName = "apps-bas1-pip1" 65 | subnetAddressPrefix = "192.168.1.224/27" 66 | tags = var.default_tags 67 | } 68 | 69 | #Module's output 70 | output "bastion_id" { 71 | value = module.Az-Bastion-Demo.bastion_id 72 | } 73 | ``` -------------------------------------------------------------------------------- /module/Az-Bastion/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_template_deployment" "bastion" { 2 | name = "${var.bastionHostName}-dep" 3 | resource_group_name = var.resourceGroupName 4 | template_body = file( 5 | "${path.module}/AzureRmBastion_template.json", 6 | ) 7 | deployment_mode = "Incremental" 8 | 9 | parameters = { 10 | location = var.location 11 | resourceGroupName = var.resourceGroupName 12 | bastionHostName = var.bastionHostName 13 | subnetName = var.subnetName 14 | publicIpAddressName = var.publicIpAddressName 15 | existingVNETName = var.existingVNETName 16 | subnetAddressPrefix = var.subnetAddressPrefix 17 | tags = jsonencode(var.tags) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /module/Az-Bastion/output.tf: -------------------------------------------------------------------------------- 1 | output "bastion_id" { 2 | value = azurerm_template_deployment.bastion.outputs["resourceID"] 3 | } 4 | -------------------------------------------------------------------------------- /module/Az-Bastion/var.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "location" { 3 | type = string 4 | description = "Location of the Bastion." 5 | } 6 | 7 | variable "resourceGroupName" { 8 | type = string 9 | description = "Resource group name of the Bastion." 10 | } 11 | 12 | variable "bastionHostName" { 13 | type = string 14 | description = "Name of the Bastion." 15 | } 16 | 17 | variable "subnetName" { 18 | type = string 19 | description = "Subnet name of the Bastion." 20 | default = "AzureBastionSubnet" 21 | } 22 | 23 | variable "publicIpAddressName" { 24 | type = string 25 | description = "Public IP address name." 26 | } 27 | 28 | variable "existingVNETName" { 29 | type = string 30 | description = "Vnet name of the Bastion." 31 | } 32 | 33 | variable "subnetAddressPrefix" { 34 | type = string 35 | description = "Subnet prefix that is dedicated to the Bastion." 36 | } 37 | 38 | variable "tags" { 39 | type = map(string) 40 | description = "Tags of the bastion." 41 | } 42 | -------------------------------------------------------------------------------- /module/Az-Bastion/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12" 3 | } 4 | -------------------------------------------------------------------------------- /module/Az-KeyVault/README.md: -------------------------------------------------------------------------------- 1 | Usage 2 | ----- 3 | 4 | ```hcl 5 | #Set the Provider 6 | provider "azurerm" { 7 | tenant_id = var.tenant_id 8 | subscription_id = var.subscription_id 9 | client_id = var.client_id 10 | client_secret = var.client_secret 11 | version = "~> 2.0" 12 | features {} 13 | } 14 | provider "azuread" { 15 | tenant_id = var.tenant_id 16 | subscription_id = var.subscription_id 17 | client_id = var.client_id 18 | client_secret = var.client_secret 19 | } 20 | 21 | #Set authentication variables 22 | variable "tenant_id" { 23 | description = "Azure tenant Id." 24 | } 25 | 26 | variable "subscription_id" { 27 | description = "Azure subscription Id." 28 | } 29 | 30 | variable "client_id" { 31 | description = "Azure service principal application Id." 32 | } 33 | 34 | variable "client_secret" { 35 | description = "Azure service principal application Secret." 36 | } 37 | 38 | #Set resource variables 39 | variable "app_name" { 40 | default = "jdld" 41 | } 42 | 43 | variable "env_name" { 44 | default = "sand1" 45 | } 46 | 47 | variable "default_tags" { 48 | type = map(string) 49 | 50 | default = { 51 | ENV = "sand1" 52 | APP = "JDLD" 53 | BUD = "FR_BXXXXX" 54 | CTC = "j.dumont@veebaze.com" 55 | } 56 | } 57 | 58 | #Call resource / module 59 | data "azuread_service_principal" "demo" { 60 | application_id = var.client_id 61 | } 62 | 63 | module "Az-KeyVault-demo" { 64 | source = "github.com/JamesDLD/terraform/module/Az-KeyVault" 65 | key_vaults = [ 66 | { 67 | suffix_name = "sci" 68 | policy1_tenant_id = var.tenant_id 69 | policy1_object_id = data.azuread_service_principal.demo.object_id 70 | policy1_application_id = data.azuread_service_principal.demo.application_id 71 | }, 72 | ] 73 | kv_tenant_id = var.tenant_id 74 | kv_prefix = "${var.app_name}-${var.env_name}-" 75 | kv_suffix = "-kv1" 76 | kv_location = "francecentral" 77 | kv_resource_group_name = "infr-jdld-noprd-rg1" 78 | kv_sku = "standard" 79 | kv_tags = var.default_tags 80 | } 81 | ``` -------------------------------------------------------------------------------- /module/Az-KeyVault/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_key_vault" "key_vaults" { 2 | count = "${length(var.key_vaults)}" 3 | name = "${var.kv_prefix}${var.key_vaults[count.index]["suffix_name"]}${var.kv_suffix}" 4 | location = "${var.kv_location}" 5 | resource_group_name = "${var.kv_resource_group_name}" 6 | tenant_id = "${var.kv_tenant_id}" 7 | enabled_for_deployment = true 8 | enabled_for_disk_encryption = true 9 | enabled_for_template_deployment = true 10 | sku_name = "${var.kv_sku}" 11 | 12 | tags = "${var.kv_tags}" 13 | 14 | access_policy { 15 | tenant_id = var.key_vaults[count.index]["policy1_tenant_id"] 16 | object_id = var.key_vaults[count.index]["policy1_object_id"] 17 | application_id = var.key_vaults[count.index]["policy1_application_id"] 18 | 19 | certificate_permissions = [ 20 | "create", 21 | "delete", 22 | "deleteissuers", 23 | "get", 24 | "getissuers", 25 | "import", 26 | "list", 27 | "listissuers", 28 | "managecontacts", 29 | "manageissuers", 30 | "setissuers", 31 | "update", 32 | ] 33 | 34 | key_permissions = [ 35 | "backup", 36 | "create", 37 | "decrypt", 38 | "delete", 39 | "encrypt", 40 | "get", 41 | "import", 42 | "list", 43 | "purge", 44 | "recover", 45 | "restore", 46 | "sign", 47 | "unwrapKey", 48 | "update", 49 | "verify", 50 | "wrapKey", 51 | ] 52 | 53 | secret_permissions = [ 54 | "backup", 55 | "delete", 56 | "get", 57 | "list", 58 | "purge", 59 | "recover", 60 | "restore", 61 | "set", 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /module/Az-KeyVault/output.tf: -------------------------------------------------------------------------------- 1 | output "kv_ids" { 2 | value = azurerm_key_vault.key_vaults.*.id 3 | } 4 | 5 | output "kv_vault_uri" { 6 | value = azurerm_key_vault.key_vaults.*.vault_uri 7 | } 8 | -------------------------------------------------------------------------------- /module/Az-KeyVault/var.tf: -------------------------------------------------------------------------------- 1 | variable "key_vaults" { 2 | description="List containing your key vaults." 3 | type = list(object({ 4 | suffix_name = string 5 | policy1_tenant_id = string 6 | policy1_object_id = string 7 | policy1_application_id = string 8 | })) 9 | } 10 | 11 | variable "kv_prefix" { 12 | description="Key vault prefix name." 13 | type = string 14 | } 15 | variable "kv_suffix" { 16 | description="Key vault suffix name." 17 | type = string 18 | } 19 | variable "kv_location" { 20 | description="Key vault location." 21 | type = string 22 | } 23 | variable "kv_resource_group_name" { 24 | description="Key vault Resource group name." 25 | type = string 26 | } 27 | variable "kv_sku" { 28 | description="Key vault SKU." 29 | type = string 30 | } 31 | variable "kv_tenant_id" { 32 | description="Key vault Azure Tenant Id." 33 | type = string 34 | } 35 | 36 | variable "kv_tags" { 37 | description="Key vault tags." 38 | type = map(string) 39 | } 40 | -------------------------------------------------------------------------------- /module/Az-KeyVault/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12" 3 | } 4 | -------------------------------------------------------------------------------- /module/Az-LoadBalancer/README.md: -------------------------------------------------------------------------------- 1 | WARNING 2 | ----- 3 | Module has been moved [here on Terraform public registry](https://registry.terraform.io/modules/JamesDLD/Az-LoadBalancer/azurerm). -------------------------------------------------------------------------------- /module/Az-PolicyAssignment/README.md: -------------------------------------------------------------------------------- 1 | Usage 2 | ----- 3 | 4 | ```hcl 5 | #Set the Provider 6 | provider "azurerm" { 7 | tenant_id = var.tenant_id 8 | subscription_id = var.subscription_id 9 | client_id = var.client_id 10 | client_secret = var.client_secret 11 | version = "~> 2.0" 12 | features {} 13 | } 14 | 15 | #Set authentication variables 16 | variable "tenant_id" { 17 | description = "Azure tenant Id." 18 | } 19 | 20 | variable "subscription_id" { 21 | description = "Azure subscription Id." 22 | } 23 | 24 | variable "client_id" { 25 | description = "Azure service principal application Id." 26 | } 27 | 28 | variable "client_secret" { 29 | description = "Azure service principal application Secret." 30 | } 31 | 32 | #Set resource variables 33 | variable "policies" { 34 | default = [ 35 | { 36 | suffix_name = "enforce-nsg-on-subnet" #Used to name the policy and to call json template files located into the module's folder 37 | policy_type = "Custom" 38 | mode = "All" 39 | }, 40 | { 41 | suffix_name = "enforce-udr-on-subnet" #Used to name the policy and to call json template files located into the module's folder 42 | policy_type = "Custom" 43 | mode = "All" 44 | }, 45 | ] 46 | } 47 | 48 | variable "apps_nsgs" { 49 | default = [ 50 | { 51 | id = "1" 52 | security_rules = [ 53 | { 54 | description = "Demo1" 55 | direction = "Inbound" 56 | name = "ALL_to_NIC_tcp-3389" 57 | access = "Allow" 58 | priority = "2000" 59 | source_address_prefix = "*" 60 | destination_address_prefix = "*" 61 | destination_port_range = "3389" 62 | protocol = "tcp" 63 | source_port_range = "*" 64 | }, 65 | ] 66 | }, 67 | ] 68 | } 69 | 70 | variable "default_tags" { 71 | default = { 72 | ENV = "sand1" 73 | APP = "JDLD" 74 | BUD = "FR_BXXXXX" 75 | CTC = "j.dumont@veebaze.com" 76 | } 77 | } 78 | 79 | #Call module 80 | module "Az-PolicyDefinition-Demo" { 81 | source = "../../Az-PolicyDefinition" 82 | policies = var.policies 83 | pol_prefix = "ApplicationToto-" 84 | pol_suffix = "-pol1" 85 | } 86 | 87 | resource "azurerm_virtual_network" "demo" { 88 | name = "infra-demo-net1" 89 | location = "francecentral" 90 | resource_group_name = "infr-jdld-noprd-rg1" 91 | address_space = ["10.0.6.0/24", "10.0.7.0/24"] 92 | tags = var.default_tags 93 | } 94 | 95 | resource "azurerm_network_security_group" "demo" { 96 | count = length(var.apps_nsgs) 97 | name = "jdld-demo-nsg${var.apps_nsgs[count.index]["id"]}" 98 | location = "francecentral" 99 | resource_group_name = "infr-jdld-noprd-rg1" 100 | 101 | dynamic "security_rule" { 102 | for_each = var.apps_nsgs[count.index]["security_rules"] 103 | content { 104 | description = lookup(security_rule.value, "description", null) 105 | direction = lookup(security_rule.value, "direction", null) 106 | name = lookup(security_rule.value, "name", null) 107 | access = lookup(security_rule.value, "access", null) 108 | priority = lookup(security_rule.value, "priority", null) 109 | source_address_prefix = lookup(security_rule.value, "source_address_prefix", null) 110 | source_address_prefixes = lookup(security_rule.value, "source_address_prefixes", null) 111 | destination_address_prefix = lookup(security_rule.value, "destination_address_prefix", null) 112 | destination_address_prefixes = lookup(security_rule.value, "destination_address_prefixes", null) 113 | destination_port_range = lookup(security_rule.value, "destination_port_range", null) 114 | destination_port_ranges = lookup(security_rule.value, "destination_port_ranges", null) 115 | protocol = lookup(security_rule.value, "protocol", null) 116 | source_port_range = lookup(security_rule.value, "source_port_range", null) 117 | source_port_ranges = lookup(security_rule.value, "source_port_ranges", null) 118 | } 119 | } 120 | tags = var.default_tags 121 | } 122 | 123 | module "Az-PolicyAssignment-Infra-nsg-on-subnet" { 124 | source = "github.com/JamesDLD/terraform/module/Az-PolicyAssignment" 125 | p_ass_name = "enforce-nsg-on-subnet-for-infra-vnet" 126 | p_ass_scope = element(azurerm_virtual_network.demo.*.id, 0) 127 | p_ass_policy_definition_id = module.Az-PolicyDefinition-Demo.policy_ids[0] 128 | p_ass_key_parameter1 = "nsgId" 129 | p_ass_value_parameter1 = element(azurerm_network_security_group.demo.*.id, 0) 130 | } 131 | ``` -------------------------------------------------------------------------------- /module/Az-PolicyAssignment/main.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_client_config" "current" { 2 | } 3 | 4 | resource "azurerm_policy_assignment" "assignments" { 5 | name = var.p_ass_name 6 | scope = var.p_ass_scope 7 | policy_definition_id = var.p_ass_policy_definition_id 8 | description = "Assigned by App Id : ${data.azurerm_client_config.current.service_principal_application_id}" 9 | display_name = var.p_ass_name 10 | 11 | parameters = < 120 | virtual_network_name = lookup(azurerm_recovery_services_vault.Infr, each.value["rsv_key"], null)["name"] 121 | 122 | Workaround is to perform an explicit depedency--> 123 | */ 124 | /* 125 | depends_on = [azurerm_recovery_services_vault.Infr] 126 | recovery_vault_name = "${lookup(var.recovery_services_vault, each.value["rsv_key"], "wrong_rsv_key_in_rsvpol")["prefix"]}-${var.app_name}-${var.env_name}-rsv${lookup(var.recovery_services_vault, each.value["rsv_key"], "wrong_rsv_key_in_rsvpol")["id"]}" 127 | } 128 | */ 129 | -------------------------------------------------------------------------------- /module/Create-AzureRmRecoveryServicesVault/var.tf: -------------------------------------------------------------------------------- 1 | variable "rsv_name" { 2 | description="Recovery services vault name." 3 | type=string 4 | } 5 | 6 | variable "rsv_resource_group_name" { 7 | description="Recovery services vault rg name." 8 | type=string 9 | } 10 | 11 | variable "rsv_tags" { 12 | description="Recovery services vault tags." 13 | type = map(string) 14 | } 15 | 16 | variable "rsv_backup_policies" { 17 | description="Recovery services vault backup policies." 18 | type = list(object({ 19 | Name = string 20 | scheduleRunFrequency = string 21 | scheduleRunDays = string 22 | scheduleRunTimes = string 23 | timeZone = string 24 | dailyRetentionDurationCount = number 25 | weeklyRetentionDurationCount = number 26 | monthlyRetentionDurationCount = number 27 | })) 28 | } -------------------------------------------------------------------------------- /module/Create-AzureRmRecoveryServicesVault/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /module/Enable-AzureRmVirtualNetworkPeering/output.tf: -------------------------------------------------------------------------------- 1 | output "peers_ops" { 2 | value = azurerm_virtual_network_peering.one_dest_to_source.*.id 3 | } 4 | 5 | -------------------------------------------------------------------------------- /module/Enable-AzureRmVirtualNetworkPeering/var.tf: -------------------------------------------------------------------------------- 1 | variable "vnet_src_name" { 2 | } 3 | 4 | variable "vnet_rg_src_name" { 5 | } 6 | 7 | variable "vnet_src_id" { 8 | } 9 | 10 | provider "azurerm" { 11 | alias = "src" 12 | } 13 | 14 | variable "Disable_Vnet_Peering" { 15 | } 16 | 17 | ############################################################# 18 | ########## ops ########## 19 | ############################################################# 20 | variable "list_one" { 21 | description="Virtual network names and resource group names list." 22 | type = list(object({ 23 | name = string 24 | resource_group_name = string 25 | })) 26 | } 27 | 28 | provider "azurerm" { 29 | alias = "provider_one" 30 | } 31 | 32 | ############################################################# 33 | ########## sec ########## 34 | ############################################################# 35 | variable "list_two" { 36 | description="Virtual network names and resource group names list." 37 | type = list(object({ 38 | name = string 39 | resource_group_name = string 40 | })) 41 | } 42 | 43 | provider "azurerm" { 44 | alias = "provider_two" 45 | } 46 | 47 | ############################################################# 48 | ########## k8s ########## 49 | ############################################################# 50 | variable "list_three" { 51 | description="Virtual network names and resource group names list." 52 | type = list(object({ 53 | name = string 54 | resource_group_name = string 55 | })) 56 | } 57 | 58 | provider "azurerm" { 59 | alias = "provider_three" 60 | } 61 | 62 | ############################################################# 63 | ########## Agile Fabric ########## 64 | ############################################################# 65 | variable "list_four" { 66 | description="Virtual network names and resource group names list." 67 | type = list(object({ 68 | name = string 69 | resource_group_name = string 70 | })) 71 | } 72 | 73 | provider "azurerm" { 74 | alias = "provider_four" 75 | } 76 | 77 | ############################################################# 78 | ########## Pub ########## 79 | ############################################################# 80 | variable "list_five" { 81 | description="Virtual network names and resource group names list." 82 | type = list(object({ 83 | name = string 84 | resource_group_name = string 85 | })) 86 | } 87 | 88 | provider "azurerm" { 89 | alias = "provider_five" 90 | } 91 | 92 | ############################################################# 93 | ########## Extra 1 ########## 94 | ############################################################# 95 | variable "list_six" { 96 | description="Virtual network names and resource group names list." 97 | type = list(object({ 98 | name = string 99 | resource_group_name = string 100 | })) 101 | } 102 | 103 | provider "azurerm" { 104 | alias = "provider_six" 105 | } 106 | 107 | ############################################################# 108 | ########## Extra 2 ########## 109 | ############################################################# 110 | variable "list_seven" { 111 | description="Virtual network names and resource group names list." 112 | type = list(object({ 113 | name = string 114 | resource_group_name = string 115 | })) 116 | } 117 | 118 | provider "azurerm" { 119 | alias = "provider_seven" 120 | } 121 | 122 | ############################################################# 123 | ########## Extra 3 ########## 124 | ############################################################# 125 | variable "list_eight" { 126 | description="Virtual network names and resource group names list." 127 | type = list(object({ 128 | name = string 129 | resource_group_name = string 130 | })) 131 | } 132 | 133 | provider "azurerm" { 134 | alias = "provider_eight" 135 | } 136 | 137 | -------------------------------------------------------------------------------- /module/Enable-AzureRmVirtualNetworkPeering/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /module/Get-AzureRmVirtualNetwork/README.md: -------------------------------------------------------------------------------- 1 | Usage 2 | ----- 3 | ```hcl 4 | #Set the Provider 5 | provider "azurerm" { 6 | tenant_id = var.tenant_id 7 | subscription_id = var.subscription_id 8 | client_id = var.client_id 9 | client_secret = var.client_secret 10 | version = "~> 2.0" 11 | features {} 12 | } 13 | 14 | #Set authentication variables 15 | variable "tenant_id" { 16 | description = "Azure tenant Id." 17 | } 18 | 19 | variable "subscription_id" { 20 | description = "Azure subscription Id." 21 | } 22 | 23 | variable "client_id" { 24 | description = "Azure service principal application Id." 25 | } 26 | 27 | variable "client_secret" { 28 | description = "Azure service principal application Secret." 29 | } 30 | 31 | #Set resource variables 32 | #N/A 33 | 34 | #Call module 35 | module "Get-AzureRmVirtualNetwork" { 36 | source = "github.com/JamesDLD/terraform/module/Get-AzureRmVirtualNetwork" 37 | vnets = ["bp1-vnet1"] 38 | vnet_resource_group_name = "infr-jdld-noprd-rg1" 39 | } 40 | ``` -------------------------------------------------------------------------------- /module/Get-AzureRmVirtualNetwork/main.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_virtual_network" "vnets" { 2 | count = length(var.vnets) 3 | name = element(var.vnets, count.index) 4 | resource_group_name = var.vnet_resource_group_name 5 | } 6 | 7 | -------------------------------------------------------------------------------- /module/Get-AzureRmVirtualNetwork/output.tf: -------------------------------------------------------------------------------- 1 | output "vnet_names" { 2 | value = data.azurerm_virtual_network.vnets.*.name 3 | } 4 | 5 | output "vnet_ids" { 6 | value = data.azurerm_virtual_network.vnets.*.id 7 | } 8 | 9 | -------------------------------------------------------------------------------- /module/Get-AzureRmVirtualNetwork/var.tf: -------------------------------------------------------------------------------- 1 | variable "vnets" { 2 | type = list(string) 3 | } 4 | 5 | variable "vnet_resource_group_name" { 6 | } 7 | 8 | -------------------------------------------------------------------------------- /module/Get-AzureRmVirtualNetwork/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /pipeline/PublishBuildArtifacts.yml: -------------------------------------------------------------------------------- 1 | # Publish Build Artifact 2 | parameters: 3 | ArtifactName: '' 4 | 5 | steps: 6 | - task: PublishBuildArtifacts@1 7 | displayName: '${{ parameters.ArtifactName }} - Publish Artifact: ${{ parameters.ArtifactName }}' 8 | inputs: 9 | PathtoPublish: './ArtifactPublishLocation' 10 | ArtifactName: '${{ parameters.ArtifactName }}' --------------------------------------------------------------------------------