Terraform: Is 'Depends On' required? Or when 'Depends On' is required? - azure

I have the following Terraform Code
Is 'Depends On' required in the resource block 'azurerm_dns_zone'? If not, when do I need to use 'Depends On'?
// Resource Group
resource "random_pet" "rg-name" {
prefix = var.resource_group_name_prefix
}
// Public DNS Zone
resource "azurerm_dns_zone" "dns_zone" {
name = "eat-eggs.ca"
resource_group_name = azurerm_resource_group.rg.name
depends_on = [
azurerm_resource_group.rg
]
}

In your case depends_on is not required, as you already are referring to it in:
resource_group_name = azurerm_resource_group.rg.name
This means that azurerm_resource_group.rg must be already available before azurerm_dns_zone.dns_zone can be created.
You only need depends_on if there is no such reference done. In such a case, TF does not "know" if a given resource is dependent on something else.

Related

How to pass certain attributes from resources that were created with for_each loop to other resources in Terraform?

I have been trying to figure out what would be the most ideal option to deploy some fundamental, mostly identical resources (vnet, subnet, bastion host, nsg, etc.) resources in Azure, using Terraform.
I have tried it with for_each and it was working just fine until I have faced a problem where I had to pass a value to an attribute from a resource which was created with for_each. Let me show you:
So this is obviously working, nothing wrong with the following resources:
resource "azurerm_subnet" "AzureBastionSubnet" {
for_each = var.bastion_subnet
name = each.value["name"]
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet[each.key].name
address_prefixes = each.value["address_prefixes"]
depends_on = [azurerm_virtual_network.vnet]
}
resource "azurerm_public_ip" "bastion_public_ip" {
for_each = toset(var.public_ip_location)
name = "bastion-public-ip-${each.value}"
location = each.value
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Static"
sku = "Standard"
depends_on = [azurerm_subnet.AzureBastionSubnet]
}
But the problem starts now when in the following resource I need to pass attribute values from resources which were created with for_each. How on earth do I pass the right attributes from the created bastion subnets and public IPs to the subnet_id and public_ip_address_id?
resource "azurerm_bastion_host" "bastion" {
for_each = toset(var.location_list)
name = "bastion-${each.value}"
location = each.value
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = "configuration"
subnet_id = azurerm_subnet.AzureBastionSubnet.id
public_ip_address_id = azurerm_public_ip.bastion_public_ip.id
}
depends_on = [azurerm_public_ip.bastion_public_ip]
}
Thanks!
I was looking into Terraform's lookup, and also the for loop and I am sure they could make it work but I just cannot seem to figure it out.
You might be creating multiple items of azurerm_subnet.AzureBastionSubnet as you are using for_each here
resource "azurerm_subnet" "AzureBastionSubnet" {
for_each = var.bastion_subnet
name = each.value["name"]
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet[each.key].name
address_prefixes = each.value["address_prefixes"]
depends_on = [azurerm_virtual_network.vnet]
}
So you may want to refer to your individual azurerm_subnet instance by passing your var.bastion_subnet set member, or its map key.
for example:
resource "azurerm_bastion_host" "bastion" {
..
ip_configuration {
...
subnet_id = azurerm_subnet.AzureBastionSubnet["subnet-1"].id
}
depends_on = [azurerm_public_ip.bastion_public_ip]
}
Where subnet-1 is a key in my var.bastion_subnet map.
From Terraform documentation:
Referring to Instances
When for_each is set, Terraform distinguishes between the block
itself and the multiple resource or module instances associated with
it. Instances are identified by a map key (or set member) from the
value provided to for_each.
<TYPE>.<NAME> or module.<NAME> (for example,
azurerm_resource_group.rg) refers to the block. <TYPE>.<NAME>[<KEY>]
or module.<NAME>[<KEY>] (for example,
azurerm_resource_group.rg["a_group"],
azurerm_resource_group.rg["another_group"], etc.) refers to individual
instances.

Can't make feature prevent_deletion_if_contains_resources working in Terraform

Context
Trying to understand how is working feature prevent_deletion_if_contains_resources in AzureRm on Terraform:
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = true
}
}
}
The documentation:
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/features-block#resource_group
Says:
Should the azurerm_resource_group resource check that there are no
Resources within the Resource Group during deletion? This means that
all Resources within the Resource Group must be deleted prior to
deleting the Resource Group. Defaults to true.
My issue
Whatever the value of prevent_deletion_if_contains_resources this never happens.
Terraform destroy work as fine
I can delete the Resource Group from the portal
What I did
This is the full script:
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = true
}
}
}
provider "azurerm" {
alias = "autreChoix"
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
}
}
resource "azurerm_resource_group" "rg2" {
name = "rg2"
location = "northeurope"
provider = azurerm.autreChoix
}
resource "azurerm_resource_group" "rg" {
name = "rg1"
location = "westeurope"
}
resource "azurerm_storage_account" "sa" {
name = "mystor1"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_storage_account" "sa2" {
name = "mystor2"
resource_group_name = azurerm_resource_group.rg2.name
location = azurerm_resource_group.rg2.location
account_tier = "Standard"
account_replication_type = "LRS"
}
Creates 2 RG. On with each value of prevent_deletion_if_contains_resources.
What I need
Does anybody tells me what I am missing?
Thanks
The main problem prevent_deletion_if_contains_resources solves is throwing a warning to the Terraform user when there are additional Resources within a Resource Group that it is trying to remove that it does not manage, since those Resources will also be deleted when the Resource Group is deleted by Terraform.
Here is the original issue: https://github.com/hashicorp/terraform-provider-azurerm/issues/1608, and later the default behavior in the provider was changed to be true as a result of this other issue: https://github.com/hashicorp/terraform-provider-azurerm/issues/13777.
That setting only applies to using Terraform. It does not prevent users in the Azure portal from deleting the Resource Group. However, I suspect that if you create the Resource Group using Terraform, then add a new Resource within that Resource Group using the Azure portal, and then finally perform a terraform destroy on that Resource Group, you should see an Terraform error if you have prevent_deletion_if_contains_resources set to true.

How Do I Skip The Creation Of A Terraform Resource?

My terraform script is setup up for a web app in production.
As part of that I have Azure DDoS protection enabled.
However, this is really expensive compared to the rest of the infrastructure.
For this reason, I don't want to create it for my development environment.
I run terraform using Azure pipelines so I would like to configure the pipeline to optionally not create it. e.g. with a variable in the pipeline
Is there an option I can pass to terraform to skip this resource?
Assuming there is an option and I can skip the ddos resource, will the creation of the vnet fail in the snippet below if it doesn't exist?
#---------------------------------------
# DDOS Protection Plan Definition
#---------------------------------------
resource "azurerm_network_ddos_protection_plan" "ddos" {
name = var.ddos_plan_name
location = var.location
resource_group_name = azurerm_resource_group.rg.name
}
#---------------------------------------
# vNet Definition
#---------------------------------------
resource "azurerm_virtual_network" "vnet" {
name = lower("${local.vNet_id}-${var.location_id}-${var.env_id}-1")
resource_group_name = azurerm_resource_group.rg.name
location = var.location
address_space = var.address_space
ddos_protection_plan {
id = azurerm_network_ddos_protection_plan.ddos.id
enable = true
}
depends_on = [
azurerm_resource_group.rg
]
}
The way I would do it is to use the count meta-argument [1]. For example, create a variable with a name create_ddos_protection_plan, set it to be of type bool and by default set it to false:
variable "create_ddos_protection_plan" {
description = "Whether to create DDoS resource or not."
type = bool
default = false
}
resource "azurerm_network_ddos_protection_plan" "ddos" {
count = var.create_ddos_protection_plan ? 1 : 0
name = var.ddos_plan_name
location = var.location
resource_group_name = azurerm_resource_group.rg.name
}
Later on if you decide you want to create it, you can set the value of the variable to true or remove the count meta-argument completely.
The vnet creation would fail if the resource does not exist based on the current setup.
[1] https://www.terraform.io/language/meta-arguments/count
You can use the count meta-argument to dynamically choose how many instances of a particular resource to create, including possibly choosing to create zero of them, which therefore effectively disables the resource altogether:
variable "enable_ddos_protection" {
type = bool
default = true
}
resource "azurerm_network_ddos_protection_plan" "ddos" {
count = var.enable_ddos_protection ? 1 : 0
name = var.ddos_plan_name
location = var.location
resource_group_name = azurerm_resource_group.rg.name
}
Since the number of instances of this resource is now dynamic, azurerm_network_ddos_protection_plan.ddos will appear as a list of objects instead of a single object. Therefore you'll also need to change how you refer to it in the virtual network configuration.
The most direct way to declare that would be to use a dynamic block to tell Terraform to generate one ddos_protection_plan block per instance of that resource, so there will be no blocks of that type if there are no protection plan instances:
resource "azurerm_virtual_network" "vnet" {
name = lower("${local.vNet_id}-${var.location_id}-${var.env_id}-1")
resource_group_name = azurerm_resource_group.rg.name
location = var.location
address_space = var.address_space
dynamic "ddos_protection_plan" {
for_each = azurerm_network_ddos_protection_plan.ddos
content {
id = ddos_protection_plan.value.id
enable = true
}
}
}
(I removed the depends_on declaration here because it was redundant with the reference in the resource_group_name argument, but the dynamic block is the main point of this example.)

Terraform can't create resource in group already exist

I would create a sample azure web app using Terraform.
I use this code to create the resoucre.
This is my main.tf file :
resource "azurerm_resource_group" "rg" {
name = var.rgname
location = var.rglocation
}
resource "azurerm_app_service_plan" "plan" {
name = var.webapp_plan_name
location = var.rglocation
resource_group_name = var.rgname
sku {
tier = var.plan_settings["tier"]
size = var.plan_settings["size"]
capacity = var.plan_settings["capacity"]
}
}
resource "azurerm_app_service" "webapp" {
name = var.webapp_name
location = var.rglocation
resource_group_name = var.rgname
app_service_plan_id = azurerm_app_service_plan.plan.id
}
and this is the variable.tf
# variables for Resource Group
variable "rgname" {
description = "(Required)Name of the Resource Group"
type = string
default = "example-rg"
}
variable "rglocation" {
description = "Resource Group location like West Europe etc."
type = string
default = "eastus2"
}
# variables for web app plan
variable "webapp_plan_name" {
description = "Name of webapp"
type = string
default = "XXXXXXXXXx"
}
variable "plan_settings" {
type = map(string)
description = "Definition of the dedicated plan to use"
default = {
kind = "Linux"
size = "S1"
capacity = 1
tier = "Standard"
}
}
variable "webapp_name" {
description = "Name of webapp"
type = string
default = "XXXXXXXX"
}
The terraform apply --auto-approve show a error :
Error: creating/updating App Service Plan "XXXXXXXXXXX" (Resource Group "example-rg"): web.AppServicePlansClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: Code="ResourceGroupNotFound" Message="Resource group 'example-rg' could not be found."
but in Azure Portal , the ressource group is created
what's wrong in my code ?
Can i hold on Terraform to check if resource is created or not before pass to next resource ?
You need to reference the resources to indicate the dependency between them to terraform, so that it can guarantee, that the resource group is created first, and then the other resources. The error indicates, that the resource group does not exist, yet. Your code tries to create the ASP first and then the RG.
resource "azurerm_resource_group" "rg" {
name = var.rgname
location = var.rglocation
}
resource "azurerm_app_service_plan" "plan" {
...
resource_group_name = azurerm_resource_group.rg.name
...
}
... but I can't use variable?
To answer that question and give more detail about the current issue, what's happening in your original code is that you're not referencing terraform resources in a way that terraform can use to create its dependency graph.
if var.rgname equals <my-rg-name> and azurerm_resource_group.rg.name also equals <my-rg-name> then technically, that's the same, right?
Well, no, not necessarily. They indeed have the same value. The difference is that the first one just echos the value. The second one contains the same value but it's also instruction to terraform saying, "Hey, wait a minute. We need the name value from azurerm_resource_group.rg so let me make sure I set that up first, and then I'll provision this resource"
The difference is subtle but important. Using values in this way lets Terraform understand what it has to provision first and lets it create its dependency graph. Using variables alone does not. Especially in large projects, alway try to use the resource variable value instead of just input variables which will help avoid unnecessary issues.
It's quite common to use an input variable to define certain initial data, like the name of the resource group as you did above. After that, however, every time resource_group_name is referenced in the manifests you should always use terraform generated value, so resource_group_name = azurerm_resource_group.rg.name

Terraform tried creating a "implicit dependency" but the next stage of my code still fails to find the Azure resource group just created

Would be grateful for any assistance, I thought I had nailed this one when I stumbled across the following link ...
Creating a resource group with terraform in azure: Cannot find resource group directly after creating it
However, the next stage of my code is still failing...
Error: Code="ResourceGroupNotFound" Message="Resource group 'ShowTell' could not be found
# We strongly recommend using the required_providers block to set the
# Azure Provider source and version being used
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=2.64.0"
}
}
}
# Configure the Microsoft Azure Provider
provider "azurerm" {
features {}
}
variable "resource_group_name" {
type = string
default = "ShowTell"
description = ""
}
# Create your resource group
resource "azurerm_resource_group" "example" {
name = var.resource_group_name
location = "UK South"
}
# Should be accessible from LukesContainer.uksouth.azurecontainer.io
resource "azurerm_container_group" "LukesContainer" {
name = "LukesContainer"
location = "UK South"
resource_group_name = "${var.resource_group_name}"
ip_address_type = "public"
dns_name_label = "LukesContainer"
os_type = "Linux"
container {
name = "hello-world"
image = "microsoft/aci-helloworld:latest"
cpu = "0.5"
memory = "1.5"
ports {
port = "443"
protocol = "TCP"
}
}
container {
name = "sidecar"
image = "microsoft/aci-tutorial-sidecar"
cpu = "0.5"
memory = "1.5"
}
tags = {
environment = "testing"
}
}
In order to create an implicit dependency you must refer directly to the object that the dependency relates to. In your case, that means deriving the resource group name from the resource group object itself, rather than from the variable you'd used to configure that object:
resource "azurerm_container_group" "LukesContainer" {
name = "LukesContainer"
location = "UK South"
resource_group_name = azurerm_resource_group.example.name
# ...
}
With the configuration you included in your question, both the resource group and the container group depend on var.resource_group_name but there was no dependency between azurerm_container_group.LukesContainer and azurerm_resource_group.example, and so Terraform is therefore free to create those two objects in either order.
By deriving the container group's resource group name from the resource group object you tell Terraform that the resource group must be processed first, and then its results used to populate the container group.

Resources