How Do I Skip The Creation Of A Terraform Resource? - terraform

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.)

Related

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.

Terraform reports a change to Application Insights key on every plan that is run

I have several Azure resources that are created using the for_each property and then those resources have an Application Insights resource created using for_each as well.
Here is the code that creates the azurerm_application_insights:
resource "azurerm_application_insights" "applicationInsights" {
for_each = toset(keys(merge(local.appServices, local.functionApps)))
name = lower(join("-", ["wb", var.deploymentEnvironment, var.location, each.key, "ai"]))
location = var.location
resource_group_name = azurerm_resource_group.rg.name
application_type = "web"
lifecycle {
ignore_changes = [tags]
}
}
I've noticed that every time we run a terraform plan against some environments, we are always seeing Terraform report a "change" to the APPINSIGHTS_INSTRUMENTATIONKEY value. When I compare this value in the app settings key/value list to the actual AI instrumentation key that was created for it, it does match.
Terraform will perform the following actions:
# module.primaryRegion.module.functionapp["fnapp1"].azurerm_function_app.fnapp will be updated in-place
~ resource "azurerm_function_app" "fnapp" {
~ app_settings = {
# Warning: this attribute value will be marked as sensitive and will
# not display in UI output after applying this change
~ "APPINSIGHTS_INSTRUMENTATIONKEY" = (sensitive)
# (1 unchanged element hidden)
Is this a common issue with other people? I would think that the instrumentation key would never change especially since Terraform is what created all of these Application Insights resources and assigns it to each application.
This is how I associate each Application Insights resource to their appropriate application with a for_each property
module "webapp" {
for_each = local.appServices
source = "../webapp"
name = lower(join("-", ["wb", var.deploymentEnvironment, var.location, each.key, "app"]))
location = var.location
resource_group_name = azurerm_resource_group.rg.name
app_service_plan_id = each.value.app_service_plan_id
app_settings = merge({"APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.applicationInsights[each.key].instrumentation_key}, each.value.app_settings)
allowed_origins = each.value.allowed_origins
deploymentEnvironment = var.deploymentEnvironment
}
I'm wondering if the merge is just reordering the list of key/values in the app_settings for the app, and Terraform detects that as some kind of change and the value itself isn't changing. This is the only way I know how to assign a bunch of Application Insights resources to many web apps using for_each to reduce configuration code.
Use only the Site_config block to solve the issue
Example
resource "azurerm_windows_function_app" "function2" {
provider = azurerm.private
name = local.private.functionapps.function2.name
resource_group_name = local.private.rg.app.name
location = local.private.location
storage_account_name = local.private.functionapps.storageaccount.name
storage_account_access_key = azurerm_storage_account.function_apps_storage.primary_access_key
service_plan_id = azurerm_service_plan.app_service_plan.id
virtual_network_subnet_id = lookup(azurerm_subnet.subnets, "appservice").id
https_only = true
site_config {
application_insights_key = azurerm_application_insights.appinisghts.instrumentation_key
}
}

Using terraform, how to create multiple resources of same type with unique and unidentical names using list/count for azure?

Here is a basic example for what I am trying to achieve. I have two files (main.tf) and (variable.tf), I want to create two resource groups and in the variables file is a list of names which I want the resource groups to occupy. First name of the first resource group and similarly going forward.
So help me out on how to achieve it. I am using terraform v0.13.
main.tf file:
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
count = 2
name = var.resource_group_name
location = var.location
}
variable.tf file:
variable "resource_group_name" {
description = "Default resource group name that the network will be created in."
type = list
default = ["asd-rg","asd2-rg"]
}
variable "location" {
description = "The location/region where the core network will be created.
default = "westus"
}
You just need to change the resource group block like this:
resource "azurerm_resource_group" "test" {
count = 2
name = element(var.resource_group_name, count.index)
location = var.location
}
You can use the for_each syntax to create multiple resource of similar type. It requires a set (of unique values) to iterate over, hence convert your variable resource_group_name to set.
variable.tf
variable "resource_group_name" {
description = "Default resource group name that the network will be created in."
type = list(string)
default = ["asd-rg","asd2-rg"]
}
variable "location" {
description = "The location/region where the core network will be created.
default = "westus"
}
main.tf
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
name = each.value // value from iteration
location = var.location
for_each = toset(var.resource_group_name) // convert list to set and iterate over it
}
Edit: variable can be of type string, list or map. This needs to be converted to set to be used with for_each
for you can use the combination of length and count to create multiple resources of the same type with unique and unidentical names.
You just need to change the resource group block like this:
resource "azurerm_resource_group" "test" {
count = length(var.resource_group_name)
name = element(concat(var.resource_group_name, [""]), count.index)
location = var.location
}

How to use existing resources from another resource group to make deployments in new resource group using Terraform

I want to create resources in a new resource group but i want to use a virtual network for those resources which is in another resource group. How do i do this? For example, i want to create redis/postgresql in resourcegroupA but i want to make use of the virtual network which is in resourcegroupB. Is it possible?
This is the resource group from where i am retrieving the vnet-
resource "azurerm_resource_group" "azresourcegroup" {
name =
"resourcegroupA"
location = var.resource_group_location
}
#-----CREATING VIRTUAL NETWORK-----
resource "azurerm_virtual_network" "vnet2" {
name = "virtualnetworkA"
location = azurerm_resource_group.azresourcegroup.location
resource_group_name = azurerm_resource_group.azresourcegroup.name
address_space = [var.virtual_network_address_prefix_infra,var.virtual_network_address_prefix]
I retrieved it while using it for another resource group like this-
data "azurerm_resource_group" "azresourcegroup" {
name = "resoucegroupA"
}
data "azurerm_virtual_network" "vnet2" {
name = "virtualnetworkA"
resource_group_name = data.azurerm_resource_group.azresourcegroup.name
}
I want to use the above virtual network but want to create the other resources in the new resource group which is-
resource "azurerm_resource_group" "main" {
name = "resourcegroupB"
location = var.resource_group_location
}
I am making use of module to create redis cache that requires the vnet which is created in other RG-
module "rediscache" {
source = "../../modules/rediscache"
prefix = var.prefix
environmentType = var.environmentType
virtual_network_name = var.virtual_network_name
unique_identifier = var.unique_identifier_kube
resource_group_name = azurerm_resource_group.main.name
resource_group_location = var.resource_group_location
redis_subnet_address_prefix = var.redis_subnet_address_prefix
azurerm_virtual_network_name = data.azurerm_virtual_network.vnet2.name
azurerm_log_analytics_workspace_id = azurerm_log_analytics_workspace.workspace.id
}
To simplify this, vnet is created in other resource group and redis in another one.But i want to use that vnet. also if i change the resource group name argument used in module, from azurerm_resource_group.main.name to data.azurerm_resource_group.azresourcegroup.name then it creates the redis in the 1st resource group which i dont want.
Please help.
Of course, it's possible. The only condition is that the virtual network should be in the same location. Then you can quote it in the Terraform code via the Terraform Data Source azurerm_virtual_network like this:
data "azurerm_virtual_network" "example" {
name = "production"
resource_group_name = "networking"
}

Resources