Terraform azure-remove subcription details from output - azure

I declared security group in following way:
resource "azurerm_network_security_group" "wan" {
count = "${var.enable_wan_subnet ? 1 : 0}"
provider = "azurerm.base"
name = "${format("%s-%s", var.environment_name, "WAN-Subnet-Security-Group")}"
location = "${azurerm_resource_group.this.location}"
resource_group_name = "${azurerm_resource_group.this.name}"
tags = "${
merge(map("Name", format("%s-%s-%s",var.environment_name,"WAN-Subnets", "Security-Group")),
var.tags_global,
var.tags_module)
}"
}
and created output for that security group:
output "security_groups_id_wan" {
value = "${azurerm_network_security_group.wan.*.id}"
depends_on = [
"azurerm_subnet.wan",
]
}
In output i'm getting
Actual output
security_groups_id_wan = [
/subscriptions/111-222-333-4445/resourceGroups/default_resource_group/providers/Microsoft.Network/networkSecurityGroups/DF-DTAP-WAN-Subnet-Security-Group
]
How, from output, to remove all except resource name (DF-DTAP-WAN-Subnet-Security-Group)
Desired output:
security_groups_id_wan = [
DF-DTAP-WAN-Subnet-Security-Group
]

You can just use the Terraform functions and change the output value like this:
output "security_groups_id_wan" {
value = "${slice(split("/",azurerm_network_security_group.wan.*.id), length(split("/",azurerm_network_security_group.wan.*.id))-1, length(split("/",azurerm_network_security_group.wan.*.id)))}"
depends_on = [
"azurerm_subnet.wan",
]
}
With the functions, you can output every resource as you need. For more details, see Terraform Supported built-in functions.
Update
The test with an existing NSG through the Terraform data and the template here:
data "azurerm_network_security_group" "test" {
name = "azureUbuntu18-nsg"
resource_group_name = "charles"
}
output "substring" {
value = "${slice(split("/",data.azurerm_network_security_group.test.id), length(split("/",data.azurerm_network_security_group.test.id))-1, length(split("/",data.azurerm_network_security_group.test.id)))}"
}
The screenshot of the result here:

You built that name yourself with "${format("%s-%s", var.environment_name, "WAN-Subnet-Security-Group")}" so why not just output that?
To save repeating yourself you could put that in a local and refer to it in both the resource and the output:
locals {
security_group_name = "${format("%s-%s", var.environment_name, "WAN-Subnet-Security-Group")}"
}
resource "azurerm_network_security_group" "wan" {
count = "${var.enable_wan_subnet ? 1 : 0}"
provider = "azurerm.base"
name = "${local.security_group_name}"
# ...
}
output "security_groups_id_wan" {
value = "${local.security_group_name}"
}
Note that you also didn't need the depends_on because a) it's an output, it happens at the end of things anyway and b) you already have an implicit dependency on that resource because you used an interpolation that included the resource.
You can read more about Terraform dependencies via the Hashicorp Learn platform.

Addition to #Charles Xu's answer:Had to convert list to string first
output "subnets_id_wan" {
value = "${slice(split("/",join(",",azurerm_subnet.wan.*.id)), length(split("/",join(",",azurerm_subnet.wan.*.id)))-1, length(split("/",join(",",azurerm_subnet.wan.*.id))))}"
depends_on = [
"azurerm_subnet.wan",
]
}

Related

How to add value(s) to an object from Terraform jsondecode without copying values over to another variable?

I have a following example of Terraform resources where I fetch values from secrets manager and pass them to the Lambda function. The question is how can I add extra values to an object before passing it to environment variable without replicating the values?
resource "aws_secretsmanager_secret" "example" {
name = "example"
}
resource "aws_secretsmanager_secret_version" "example" {
secret_id = aws_secretsmanager_secret.example.id
secret_string = <<EOF
{
"FOO": "bar"
}
EOF
}
data "aws_secretsmanager_secret_version" "example" {
secret_id = aws_secretsmanager_secret.example.id
depends_on = [aws_secretsmanager_secret_version.example]
}
locals {
original_secrets = jsondecode(
data.aws_secretsmanager_secret_version.example.secret_string
)
}
resource "aws_lambda_function" "example" {
...
environment {
variables = local.original_secrets
}
}
As a pseudo code I'd like to do something like this:
local.original_secrets["LOG_LEVEL"] = "debug"
The current approach I have is just to replicate the original values and add a new but of course this is not DRY.
locals {
...
updated_secrets = {
FOO = try(local.original_secrets.FOO, "")
DEBUG = "false"
}
}
You can use Terraform merge function to produce new combined map of environment variables.
lambda_environment_variables = merge(local.lambda_secrets, local.environment_variables)

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

Get resources based on a value created using count

I am using Terraform v12.19 with the aws provider v2.34.0.
Imagine, I have a resource generated with a count value:
resource "aws_iam_role" "role" {
count = length(var.somevariable)
name = var.somevariable[count.index]
}
Later on, I want to reference one specific resource instance in that way, e. g.:
resource "aws_iam_role_policy_attachment" "polatt" {
role = aws_iam_role.role["TheRoleNameIWant"].id
policy_arn = "arn:aws:iam::aws:policy/..."
}
I don't know the index, I can just rely on the name, provided by the variable. Thats because the values of the variable are provided by an external source and the order could change...
Any ideas how to do this?
You should be able to accomplish this using the index terraform function.
Here's a minimal example using null_resources to test it out
locals {
role_names = [
"role-a",
"role-b",
"role-c",
"role-d",
]
target_role_name = "role-c"
}
resource "null_resource" "hi" {
count = length(local.role_names)
}
output "target_resource" {
value = null_resource.hi[index(local.role_names, local.target_role_name)].id
}
output "all_resources" {
value = [for r in null_resource.hi : r.id]
}
This outputs, for example
all_resources = [
"4350570701002192774",
"9173388682753384584",
"1634695740603384613",
"2098863759573339880",
]
target_resource = 1634695740603384613
So your example, I suppose, would look like
resource "aws_iam_role_policy_attachment" "polatt" {
role = aws_iam_role.role[index(var.somevariable, "TheRoleNameIWant")].id
policy_arn = "arn:aws:iam::aws:policy/..."
}
Update
Your comment below mentions that you actually have a more complicated data structure than just a list of names. I just wanted to mention that you can derive names from your JSON structure.
Assuming you have something like the following
variable "role_values" {
value = [
{
name = "foo",
other = "details",
fields = 3
},
{
name = "bar",
other = "yet more details",
fields = 3
}
]
}
you could derive just the names by using a local and the newer for loops TF 0.12 offers
locals {
role_names = [for role in var.role_values: role.name]
}
That way you don't have to store the names twice.

How to get Subnet list from VPC with terraform

I've tried to get all subnet ids to add aws batch with terraform with following code:
data "aws_subnet_ids" "test_subnet_ids" {
vpc_id = "default"
}
data "aws_subnet" "test_subnet" {
count = "${length(data.aws_subnet_ids.test_subnet_ids.ids)}"
id = "${tolist(data.aws_subnet_ids.test_subnet_ids.ids)[count.index]}"
}
output "subnet_cidr_blocks" {
value = ["${data.aws_subnet.test_subnet.*.id}"]
}
Fortunately, it was working fine when I've tested like that. But when I tried to integrate with batch terraform like:
resource "aws_batch_compute_environment" "test-qr-processor" {
compute_environment_name = "test-qr-processor-test"
compute_resources {
instance_role = "${aws_iam_instance_profile.test-ec2-role.arn}"
instance_type = [
"optimal"
]
max_vcpus = 256
min_vcpus = 0
security_group_ids = [
"${aws_security_group.test-processor-batch.id}"
]
subnets = ["${data.aws_subnet.test_subnet.*.id}"]
type = "EC2"
}
service_role = "${aws_iam_role.test-batch-service-role.arn}"
type = "MANAGED"
depends_on = [ "aws_iam_role_policy_attachment.test-batch-service-role" ]
}
I've encountered following error message,
Error: Incorrect attribute value type
on terraform.tf line 142, in resource
"aws_batch_compute_environment" "test-processor": 142: subnets =
["${data.aws_subnet.test_subnet.*.id}"]
Inappropriate value for attribute "subnets": element 0: string
required.
Please let me know why, thanks.
"${data.aws_subnet.test_subnet.*.id}" is already string array type.
you should input value without [ ]
write code like :
subnets = "${data.aws_subnet.test_subnet.*.id}"
See :
Here's A document about Resource: aws_batch_compute_environment

Terraform combine lookup and splat syntax

I'm trying to provision multiple Azure virtual machines with Terraform and then get the MSI identity ID for entitlements. I started with this MSI example (https://www.terraform.io/docs/providers/azurerm/authenticating_via_msi.html) that works for me and am trying to add count, i.e.
resource "azurerm_virtual_machine" "virtual_machine" {
count = "5"
name = "test"
....
identity = {
type = "SystemAssigned"
}
.....
}
resource "azurerm_virtual_machine_extension" "virtual_machine_extension" {
count = "5"
name = "test"
location = "${var.location}"
resource_group_name = "test"
virtual_machine_name = "${element(azurerm_virtual_machine.virtual_machine.*.name, count.index)}"
publisher = "Microsoft.ManagedIdentity"
type = "ManagedIdentityExtensionForWindows"
type_handler_version = "1.0"
settings = <<SETTINGS
{
"port": 50342
}
SETTINGS
}
output "vm_principals" {
# original had --- "${lookup(azurerm_virtual_machine.virtual_machine.identity[0], "principal_id"}"
value = ["${azurerm_virtual_machine.virtual_machine.*.identity[0]}"]
}
Problem is that I get an output array as following:
vm_principals = [
{
principal_id = xxxxxxxxxxxxx,
type = SystemAssigned
},
{
principal_id = yyyyyyyyyyyyy,
type = SystemAssigned
}
]
What I'm trying to get is
vm_principals = [
xxxxxxxxxxxxxxxxx,
yyyyyyyyyyyyyyyyy
]
I tried the obvious variations, but I suspect this is a Terraform limitation.
value = ["${lookup(azurerm_virtual_machine.virtual_machine.*.identity[0], "principal_id)}"]
Any thoughts?
Unfortunately, you're right. I believe you're hitting this issue. However, this will probably be solved in v0.12 of Terraform as it will introduce a revamped language (HCL). See this Hashicorp blog article for more details.
Here's how I dealt with this issue
resource "azurerm_virtual_machine" "kubenode" {
count = "3"
...
}
For a azurerm_virtual_machine resource named kubenode, you can do this:
${azurerm_virtual_machine.kubenode.*.identity.0.principal_id}
Which will return a list of principal ids. you can then do:
${azurerm_virtual_machine.kubenode.*.identity.0.principal_id[count.index]}
For example in a role-assignment scenario:
resource "azurerm_role_assignment" "kubenode-subscription-reader-role" {
count = "${azurerm_virtual_machine.kubenode.count}"
scope = "${data.azurerm_subscription.primary.id}"
role_definition_name = "Reader"
principal_id = "${azurerm_virtual_machine.kubenode.*.identity.0.principal_id[count.index]}"
}

Resources