Unable to Create Terraform Resource Group when using modules - terraform

I am optimizing my terraform code by using modules. When i create a resource group module it works perfectly well but it creates two resource groups
i.e.
Temp-AppConfiguration-ResGrp
Temp-AppServices-ResGrp
instead it should only create
Temp-AppConfiguration-ResGrp
Code Resourcegroup.tf.
resource "azurerm_resource_group" "resource" {
name = "${var.environment}-${var.name_apptype}-ResGrp"
location = var.location
tags = {
environment = var.environment
}
}
output "resource_group_name" {
value = "${var.environment}-${var.name_apptype}-ResGrp"
}
output "resource_group_location" {
value = var.location
}
Variable.tf
variable "name_apptype" {
type = string
default = "AppServices"
}
variable "environment" {
type = string
default = "Temp"
}
variable "location" {
type = string
default = "eastus"
}
Main.tf
module "resourcegroup" {
source = "../Modules"
name_apptype = "AppConfiguration"
}
I want to pass name_apptype in main.tf when calling resource group module. So that i don't need to update variable.tf every time.
Any suggestions
where i am doing wrong. Plus i am also unable to output the value, i need it so that i could pass resource group name in the next module i want to create.
Thanks

You need to do that in the Main.tf
module "resourcegroup" {
source = "../Modules"
name_apptype = "AppConfiguration"
}
module "resourcegroup-appservices" {
source = "../Modules"
name_apptype = "AppServices"
}
These create a 2 resources groups with the values that you need, additionally you can remove the default value from the name_apptype variable.
If you want to create with the same module both resource groups you need to use count to iterate over an array of names

Related

Using terraform to create multiple resources based on a set of variables

I have a set of variables in terraform.tfvars:
resource_groups = {
cow = {
name = "Cow"
location = "eastus"
},
horse = {
name = "Horse"
location = "eastus"
},
chicken = {
name = "Chicken"
location = "westus2"
},
}
my main.tf looks like this:
...
module "myapp" {
source = "./modules/myapp"
resource_groups = var.resource_groups
}
variable "resource_groups" {}
...
./modules/myapp.main.tf look like this:
module "resource_group" {
source = "../myapp.resource_group"
resource_groups = var.resource_groups
for_each = {
for key, value in try(var.resource_groups, {}) : key => value
if try(value.reuse, false) == false
}
}
variable "resource_groups" {}
and ../myapp.resource_group looks like this:
resource "azurerm_resource_group" "resource_group" {
name = var.resource_groups.cow.name
location = var.resource_groups.cow.location
}
variable "resource_groups" {}
My hope is that after terraform plan I would see that three new RGs would be set for addition. Infact I do get three new ones, but they all use the name and location of the cow RG, because I specified var.resource_groups.cow.name The problem is I have tried all kinds of different iterators in place of .cow. and I can't get terraform to use the other variables in the terraform.tfvars file. I have tried square brackets, asterisks, and other wildcards. I am stuck.
I am looking to define a resource in one place and then use that to create multiple instances of that resource per the map of variables.
Guidance would be much appreciated.
Thanks.
Bill
For this situation you'll need to decide whether your module represents one resource group or whether it represents multiple resource groups. For a module that only contains one resource anyway that decision is not particularly important, but I assume you're factoring this out into a separate module because there is something more to this than just the single resource group resource, and so you can decide between these two based on what else this module represents: do you want to repeat everything in this module, or just the resource group resource?
If you need the module to represent a single resource group then you should change its input variables to take the data about only a single resource group, and then pass the current resource group's data in your calling module block.
Inside the module:
variable "resource_group" {
type = object({
name = string
location = string
})
}
resource "azurerm_resource_group" "resource_group" {
name = var.resource_group.name
location = var.resource_group.location
}
When calling the module:
variable "resource_groups" {
type = map(
object({
name = string
location = string
})
)
}
module "resource_group" {
source = "../myapp.resource_group"
for_each = var.resource_groups
# each.value is the value of the current
# element of var.resource_groups, and
# so it's just a single resource group.
resource_group = each.value
}
With this strategy, you will declare resource instances with the following addresses, showing that the repetition is happening at the level of the whole module rather than the individual resources inside it:
module.resource_group["cow"].azurerm_resource_group.resource_group
module.resource_group["horse"].azurerm_resource_group.resource_group
module.resource_group["chicken"].azurerm_resource_group.resource_group
If you need the module to represent the full set of resource groups then the module would take the full map of resource groups as an input variable instead of using for_each on the module block. The for_each argument will then belong to the nested resource instead.
Inside the module:
variable "resource_groups" {
type = map(
object({
name = string
location = string
})
)
}
resource "azurerm_resource_group" "resource_group" {
for_each = var.resource_groups
name = each.value.name
location = each.value.location
}
When calling the module:
variable "resource_groups" {
type = map(
object({
name = string
location = string
})
)
}
module "resource_group" {
source = "../myapp.resource_group"
# NOTE: No for_each here, because we need only
# one instance of this module which will itself
# then contain multiple instances of the resource.
resource_group = var.resource_groups
}
With this strategy, you will declare resource instances with the following addresses, showing that there's only one instance of the module but multiple instances of the resource inside it:
module.resource_group.azurerm_resource_group.resource_group["cow"]
module.resource_group.azurerm_resource_group.resource_group["horse"]
module.resource_group.azurerm_resource_group.resource_group["chicken"]
It's not clear from the information you shared which of these strategies would be more appropriate in your case, because you've described this module as if it is just a thin wrapper around the azurerm_resource_group resource type and therefore it isn't really clear what this module represents, and why it's helpful in comparison to just writing an inline resource "azurerm_resource_group" block in the root module.
When thinking about which of the above designs is most appropriate for your use-case, I'd suggest considering the advice in When to Write a Module in the Terraform documentation. It can be okay to write a module that contains only a single resource block, but that's typically for more complicated resource types where the module hard-codes some local conventions so that they don't need to be re-specified throughout an organization's Terraform configurations.
If you are just passing the values through directly to the resource arguments with no additional transformation and no additional hard-coded settings then that would suggest that this module is not useful, and that it would be simpler to write the resource block inline instead.

A resource with the ID already exists - to be managed via Terraform this resource needs to be imported into the State

I would not normally ask a question like this, however I feel stuck and I do not want to hack things, but rather take the time to understand. I am new to terraform and trying to learn it, a simple task that I have set myself is to create a SQL server.
My Environment
I have some resource groups created before, anytime I try to use the same name, I get the error.
Error: A resource with the ID "/subscriptions/000000-0000-0000-0000-00000000005/resourceGroups/tf_learning" already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for "azurerm_resource_group" for more information.
Now, you look at the error and after 2 days of google research, i followed the steps here.
Using Terraform to import existing resources on Azure
and https://gmusumeci.medium.com/how-to-import-an-existing-azure-resource-in-terraform-6d585f93ea02 and Terraform resource with the ID already exists
I create a file called existing_statee.tf with the content below.
resource "azurerm_resource_group" "tf_learning" {
}
Ran
terraform import azurerm_resource_group.tf_learning /subscriptions/000000-0000-0000-0000-00000000005/resourceGroups/tf_learningterraform import azurerm_resource_group.tf_learning /subscriptions/000000-0000-0000-0000-00000000005/resourceGroups/tf_learning
I edited the file again and saved it.
resource "azurerm_resource_group" "tf_learning" {
# (resource arguments)
name = "tf_learning"
location = "UK South"
}
Then ran the following.
terraform init
terraform plan
terraform apply
To my surprise I am still getting the error.
Error: A resource with the ID "/subscriptions/00000-00000-0000-0000-00000000000/resourceGroups/tf_learning" already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for "azurerm_resource_group" for more information.
│
│ with azurerm_resource_group.RG-Terraform,
│ on main.tf line 1, in resource "azurerm_resource_group" "RG-Terraform":
│ 1: resource "azurerm_resource_group" "RG-Terraform" {
My tf.main file.
resource "azurerm_resource_group" "RG-Terraform" {
name = var.resource-group-name
location = var.my_location
}
resource "azurerm_sql_server" "test" {
name = var.my_dev_server
resource_group_name = azurerm_resource_group.RG-Terraform.name
location = azurerm_resource_group.RG-Terraform.location
version = var.my_dev_sql_version
administrator_login = var.my_dev_sql_login
administrator_login_password = "change_me"
}
resource "azurerm_sql_database" "test" {
name = var.my_dev_sql_database
resource_group_name = azurerm_resource_group.RG-Terraform.name
location = azurerm_resource_group.RG-Terraform.location
server_name = azurerm_sql_server.test.name
edition = var.my_dev_sql_database_sku
requested_service_objective_name = var.my_dev_sql_database_objective
tags = {
environment = "dev_database_build"
}
}
variables.tf file
variable "resource-group-name" {
default = "tf_learning"
description = "The prefix used for all resources in this example"
}
variable "app-service-name" {
default = "terraform-app-service"
description = "The name of the Web App"
}
variable "location" {
default = "UK South"
description = "The Azure location where all resources in this example should be created"
}
variable "my_location" {
type = string
default = "UK South"
}
variable "my_dev_server"{
type = string
default = "test-server-test"
}
variable "my_dev_sql_login" {
type = string
default = "mylogin"
}
variable "my_dev_sql_version" {
type = string
default = "12.0"
}
variable "my_dev_sql_database" {
type = string
default = "dev_db"
}
variable "my_dev_sql_database_tag" {
type = string
default = "dev sql database from TF"
}
variable "my_dev_sql_database_sku" {
type = string
default = "Basic"
}
variable "my_dev_sql_database_objective" {
type = string
default = "Basic"
}
I am lost as to what to do next, for now I will carry on researching.
I would like to point out this, you have configured in existing_statee.tf after importing the state
resource "azurerm_resource_group" "tf_learning" {
name = "tf_learning"
location = "UK South"
}
But in main.tf you also define again
resource "azurerm_resource_group" "RG-Terraform" {
name = var.resource-group-name
location = var.my_location
}
variable "resource-group-name" {
default = "tf_learning"
description = "The prefix used for all resources in this example"
}
variable "my_location" {
type = string
default = "UK South"
}
Since you have already declared this resource in existing_statee.tf, maybe you should remove it from main.tf

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
}

Terraform: How can I have one common output for all declared modules?

I have got something like this in my terraform file:
main.tf
module "airflow_tenant_one" {
source = "../modules/airflow_tenant"
name = "one-airflow"
project = var.project
cluster_name = var.cluster_name
region = var.region
kubernetes_endpoint = var.kubernetes_endpoint
tenant_domain = "one-airflow.${var.domain_name}"
}
module "airflow_tenant_two" {
source = "../modules/airflow_tenant"
name = "two-airflow"
project = var.project
cluster_name = var.cluster_name
region = var.region
kubernetes_endpoint = var.kubernetes_endpoint
tenant_domain = "two-airflow.${var.domain_name}"
}
How can I do one common output for all declared modules?
Currently, it looks like:
outputs.tf
output "chart_name_one" {
description = "The name of the chart"
value = module.airflow_tenant_one.chart_name
}
output "chart_name_two" {
description = "The name of the chart"
value = module.airflow_tenant_two.chart_name
}
I asked because in a future it is possible that I will add more modules in my main.tf file. Will be better to have one output declaration for all of them.
The module blocks you shared seem like they are all systematically configured based on some client names, so you can potentially do this using a single module block with for_each if you are using Terraform 0.13.0 or later.
locals {
tenants = toset([
"one",
"two",
])
}
module "airflow_tenant" {
for_each = local.tenants
name = "${each.key}-airflow"
project = var.project
cluster_name = var.cluster_name
region = var.region
kubernetes_endpoint = var.kubernetes_endpoint
tenant_domain = "${each.key}-airflow.${var.domain_name}"
}
output "tenant_chart_names" {
value = {
for name, tenant in module.airflow_tenant : name => tenant.chart_name
}
}
The above will cause there to be one instance of the airflow_tenant module per element of local.tenants, with addresses like this:
module.airflow_tenant["one"]
module.airflow_tenant["two"]
The for_each makes the module behave as a map of instances when you refer to it elsewhere, which is why we're able to project that map in output "tenant_chart_names" to derive a map from tenant name to chart names.
You can add and remove elements of local.tenants over time, in which case Terraform will understand that as an intent to either create a instances or destroy an instances of all of the objects described inside that module.
You can read more about this feature in Multiple Instances of a Module.

How do I make terraform skip that block while creating multiple resources in loop from a CSV file?

Hi I am trying to create a Terraform script which will take inputs from the user in the form of a CSV file and create multiple Azure resources.
For example if the user wants to create: ResourceGroup>Vnet>Subnet in bulk, he will provide input in CSV format as below:
resourcegroup,RG_location,RG_tag,domainname,DNS_Zone_tag,virtualnetwork,VNET_location,addressspace
csvrg1,eastus2,Terraform RG,test.sd,Terraform RG,csvvnet1,eastus2,10.0.0.0/16,Terraform VNET,subnet1,10.0.0.0/24
csvrg2,westus,Terraform RG2,test2.sd,Terraform RG2,csvvnet2,westus,172.0.0.0/8,Terraform VNET2,subnet1,171.0.0.0/24
I have written the following working main.tf file:
# Configure the Microsoft Azure Provider
provider "azurerm" {
version = "=1.43.0"
subscription_id = var.subscription
tenant_id = var.tenant
client_id = var.client
client_secret = var.secret
}
#Decoding the csv file
locals {
vmcsv = csvdecode(file("${path.module}/computelanding.csv"))
}
# Create a resource group if it doesn’t exist
resource "azurerm_resource_group" "myterraformgroup" {
count = length(local.vmcsv)
name = local.vmcsv[count.index].resourcegroup
location = local.vmcsv[count.index].RG_location
tags = {
environment = local.vmcsv[count.index].RG_tag
}
}
# Create a DNS Zone
resource "azurerm_dns_zone" "dnsp-private" {
count = 1
name = local.vmcsv[count.index].domainname
resource_group_name = local.vmcsv[count.index].resourcegroup
depends_on = [azurerm_resource_group.myterraformgroup]
tags = {
environment = local.vmcsv[count.index].DNS_Zone_tag
}
}
To be continued....
The issue I am facing here what is in the second resource group, the user don't want a resource type, suppose the user want to skip the DNS zone in the resource group csvrg2. How do I make terraform skip that block ?
Edit: What I am trying to achieve is "based on some condition in the CSV file, not to create azurerm_dns_zone resource for the resource group csvrg2"
I have provided an example of the CSV file, how it may look like below:
resourcegroup,RG_location,RG_tag,DNS_required,domainname,DNS_Zone_tag,virtualnetwork,VNET_location,addressspace
csvrg1,eastus2,Terraform RG,1,test.sd,Terraform RG,csvvnet1,eastus2,10.0.0.0/16,Terraform VNET,subnet1,10.0.0.0/24
csvrg2,westus,Terraform RG2,0,test2.sd,Terraform RG2,csvvnet2,westus,172.0.0.0/8,Terraform VNET2,subnet1,171.0.0.0/24
you had already the right thought in your mind using the depends_on function. Although, you're using a count inside, which causes from my understanding, that once the first resource[0] is created, Terraform sees the dependency as solved and goes ahead as well.
I found this post with a workaround which you might be able to try:
https://github.com/hashicorp/terraform/issues/15285#issuecomment-447971852
That basically tells us to create a null_resource like in that example:
variable "instance_count" {
default = 0
}
resource "null_resource" "a" {
count = var.instance_count
}
resource "null_resource" "b" {
depends_on = [null_resource.a]
}
In your example, it might look like this:
# Create a resource group if it doesn’t exist
resource "azurerm_resource_group" "myterraformgroup" {
count = length(local.vmcsv)
name = local.vmcsv[count.index].resourcegroup
location = local.vmcsv[count.index].RG_location
tags = {
environment = local.vmcsv[count.index].RG_tag
}
}
# Create a DNS Zone
resource "azurerm_dns_zone" "dnsp-private" {
count = 1
name = local.vmcsv[count.index].domainname
resource_group_name = local.vmcsv[count.index].resourcegroup
depends_on = null_resource.example
tags = {
environment = local.vmcsv[count.index].DNS_Zone_tag
}
}
resource "null_resource" "example" {
...
depends_on = [azurerm_resource_group.myterraformgroup[length(local.vmcsv)]]
}
or depending on your Terraform version (0.12+ which you're using guessing your syntax)
# Create a resource group if it doesn’t exist
resource "azurerm_resource_group" "myterraformgroup" {
count = length(local.vmcsv)
name = local.vmcsv[count.index].resourcegroup
location = local.vmcsv[count.index].RG_location
tags = {
environment = local.vmcsv[count.index].RG_tag
}
}
# Create a DNS Zone
resource "azurerm_dns_zone" "dnsp-private" {
count = 1
name = local.vmcsv[count.index].domainname
resource_group_name = local.vmcsv[count.index].resourcegroup
depends_on = [azurerm_resource_group.myterraformgroup[length(local.vmcsv)]]
tags = {
environment = local.vmcsv[count.index].DNS_Zone_tag
}
}
I hope that helps.
Greetings

Resources