Terraform outputs to another module when using a for_each - terraform

I'm having trouble referencing the outputs of a module as an input to another module. I have a module that creates the target group using a for_each. I have another module that is also using a for_each for creating a listener rule.
I would like to use the output from the target group as an input for the target group under the listener rule module - but be able to specify which target group would attach to the listener rule.
I have tried many different suggestions with numerous variations for outputs but have not been successful.
In my main, I have these two modules created for the target groups and the listener rules:
Target Groups:
module "aws_lb_target_group_ui" {
source = "../../terraform/modules/aws_lb_target_group"
for_each = var.target_group_values
target_group_name = each.value["name"]
target_group_target_type = each.value["target_type"]
target_group_port = each.value["port"]
target_group_protocol = each.value["protocol"]
target_group_slow_start = each.value["slow_start"]
/* Health Check */
target_group_health_check_path = each.value["healthcheckpath"]
target_group_health_check_port = each.value["healthcheckport"]
target_group_health_check_enabled = each.value["healthcheckenabled"]
target_group_health_check_healthy_threshold = each.value["healthy_threshold"]
target_group_health_check_interval = each.value["interval"]
target_group_health_check_timeout = each.value["timeout"]
target_group_tags = {
Environment = each.value["Environment"]
Component = each.value["Component"]
Application = each.value["Application"]
TechContact = each.value["TechContact"]
}
}
Listener Rule:
module "aws_alb_listener_rule_ui" {
depends_on = [
module.aws_lb_target_group_ui,
module.aws_alb_listener_ui_https
]
source = "../../terraform/modules/aws_lb_listener_rule_https"
listener_rule = module.aws_alb_listener_ui_https.ui_lb_listener
for_each = var.ui_https_listener_rules
listener_rule_host_header = each.value["values"]
listener_rule_target_group = each.value["target_group"]
listener_rule_action_type = var.ui_https_listener_rule_action_type
}
The resources themselves are very generic with variables but I can post those if it's helpful.
In my tfvars I have this:
Target Groups:
I have removed all the variables but just wanted to get an idea of it would replicate out in my config through the tfvars.
target_group_values = {
Application1 = {
"name" = "TargetGroup1",
},
Application2 = {
"name" = "TargetGroup2",
},
}
Listener Rules:
ui_https_listener_rule_action_type = "forward"
ui_https_listener_rules = {
Application1 = {
values = ["Host_Header_1"]
target_group = "Manul_ARN_Entry"
},
Application2 = {
values = ["Host_Header_2"]
target_group = "Manul_ARN_Entry"
},
}
If I run it in this format with the manually arn being entered from an existing target group it runs without issue. I would like to utilize the target groups I am creating, though and get the output from those groups into the code instead of the manual entry.
What I was trying to accomplish was to get the output and then update the "listener_rule_target_group" value with the output but be able to identify one of the specific target groups as it would be a 1:1 for target groups and rules.
I have not posted the output as I have not been successful in a method yet but was more looking for some help on what I should be doing with this setup.

Update:
I was able to resolve this.
I moved "values" and "target_group_values" variables into the "target_group_values" variable and deleted the "ui_https_listener_rules" variable.
This allowed me to use one variable across both modules for my for_each. From there I was able to use the below to resolve the issue.
listener_rule_target_group = module.aws_lb_target_group_kinsale_ui[each.key].arn

Related

adding multiple destinations to new relic workflows using terraform

I am trying to create a newrelic workflow using terraform modules. I am fine with creating a workflow with signle destination. But, I am trying to create a workflow with more than one destination.
slack channel ids
variable "channel_ids" {
type = set(string)
default = ["XXXXXXXXXX","YYYYYYYYY"]
}
creating notification channels using slack channel ids
resource "newrelic_notification_channel" "notification_channel" {
for_each = var.channel_ids
name = "test" # will modify if required
type = "SLACK" # will parameterize this
destination_id = "aaaaaaaaa-bbbbb-cccc-ddddd-eeeeeeeeee"
product = "IINT"
property {
key = "channelId"
value = each.value
}
}
Now I want to create something like below (two destinations)
resource "newrelic_workflow" "newrelic_workflow" {
name = "my-workflow"
muting_rules_handling = "NOTIFY_ALL_ISSUES"
issues_filter {
name = "Filter-name"
type = "FILTER"
predicate {
attribute = "accumulations.policyName"
operator = "EXACTLY_MATCHES"
values = [ "policy_name" ]
}
}
destination {
channel_id = newrelic_notification_channel.notification_channel.id
}
destination {
channel_id = newrelic_notification_channel.notification_channel.id
}
}
I tried using for_each and for loop but no luck. Any idea on how to get my desired output?
Is it possible to loop through and create multiple destinations within the same resource, like attaching multiple destination to a single workflow?
I was able to achieve this by using a dynamic block, which produces a dynamic number of destination blocks based on the number of elements of newrelic_notification_channel.notification_channel.
resource "newrelic_workflow" "newrelic_workflow" {
name = "my-workflow"
muting_rules_handling = "NOTIFY_ALL_ISSUES"
issues_filter {
name = "Filter-name"
type = "FILTER"
predicate {
attribute = "accumulations.policyName"
operator = "EXACTLY_MATCHES"
values = [ "policy_name" ]
}
}
dynamic "destination" {
for_each = newrelic_notification_channel.notification_channel
content {
channel_id = destination.value.id
}
}
}

creating a list of list objects terraform

I'm setting up a terraform repo for my snowflake instance and bringing in a list of users to start managing.
I have a module called users
and have the following files:
I have a variable defined as follows.
variable "users" {
type = list(object(
{
name = string
comment = string
default_role = string
disabled = bool
must_change_password = bool
display_name = string
email = string
first_name = string
last_name = string
default_warehouse = string
}
)
)
}
now inside users.tf I want to hold a list of all my users based on the above variable, I thought I could define it as follows:
users {
user_1 = {
name = 'x'
},
user_2 = {
name = 'y'
}
}
however, when I run Terraform validate on this it gives me the error that a user block is not expected here.
Can someone tell me my error and give me some guidance if I'm doing this correctly?
My intention is to have a file to hold all my users that I then define with a dynamic block inside my main.tf file within this module.
I can then reference the dynamic block inside the outputs.tf which will give me access to the users inside said module in the global project namespace.
Looks to me like you are attempting to configuring your users as an object:
users {
user_1 = {
name = "x"
},
user_2 = {
name = "y"
}
}
but you actually set your variable constraint to a list of objects. So it should be:
users = [
{
name = "user_1"
# other fields
},
{
name = "user_2"
# other fields
}
]
Here is a full working example:
modules/users/variables.tf
variable "users" {
type = list(object({
name = string
}))
}
modules/users/outputs.tf
output "users" {
value = var.users
}
main.tf
module "users" {
source = "./modules/users"
users = [
{ name = "user_1" },
{ name = "user_2" }
]
}
output "users" {
value = module.users.users
}
plan output
Changes to Outputs:
+ users = [
+ {
+ name = "user_1"
},
+ {
+ name = "user_2"
},
]
Your config syntax and usage is completely correct here. Your config file organization is the issue here. users.tf is a Terraform variables file, and therefore should have the .tfvars extension. If you rename the file from users.tf to e.g. users.tfvars, then you can specify it as an input with the -var-file=users.tfvars argument with the CLI or otherwise as per standard usage. You can see more information in the documentation.
On a side note: it is not really best practices to manage an entire module just for managing a set of users for a specific service. If you follow this design pattern in the future, then your codebase will not scale very well, and could easily become unmanageably large.

aws_network_interface_sg_attachment module on adding new SG , destroying existing SG

AWS instance , Elastic Network Interface (ENI) has existing security group (SG_main).
Adding new SG (sg-01) using aws_network_interface_sg_attachment resource.
sg-01 is getting added , SG_main has no affect on it.
Adding another sg-02 using aws_network_interface_sg_attachment resource.
destroy's sg-01 , attaches sg-02 and SG_main(no change).
Is there any other module don't destroy existing SG or Is there any other way to achieve this.
Input variable -
sg_attachment = [
{
attach_security_group_id = "sg-01"
}
]
Input variables are given in above form , as I was planning to give multiple values later like below.
["sg-01","sg-02","sg-03"] -> will be mapped with EC2[0]
["sg-04","sg-05"] -> will be mapped with EC2[1]
.. like wise.
sg_attachment = [
{
attach_security_group_id = ["sg-01","sg-02","sg-03"]
},
{
attach_security_group_id = ["sg-04","sg-05"]
}
]
resource block
resource "aws_network_interface_sg_attachment" "sg_attachment" {
count = length(var.sg_attachment) != 0 ? length(var.sg_attachment) : 0
security_group_id = var.sg_attachment[count.index].attach_security_group_id
network_interface_id = aws_instance.EC2[count.index].primary_network_interface_id
}
Is there any other way to achieve this.
You would have to use aws_network_interface data source to query for the existing security_groups for your ENI.
data "aws_network_interface" "existing_sgs" {
count = length(aws_instance.EC2)
id = aws_instance.EC2[count.index].primary_network_interface_id
}
Once you have existing_sgs you would have to concat them with the new ones. Otherwise, the old ones get replaced with the new ones as you already observed.

terraform - how to use variables inside attributes

I am not sure if this is the right approach to do this but I want to use a variable as an attribute.
For example, I have a variable that changes based on user input: os_name = ubuntu.
I want to use this variable name like the following,
resource "aws_instance" "workerNode" {
..................
ami = data.aws_ami.${var.os_name}.image_id
..................
}
Following is an example of the data block,
data "aws_ami" "suse" {
count = "${var.os_name == "suse" ? 1 : 0}"
owners = ["amazon"]
most_recent = true
filter {
name = "name"
values = ["suse-sles-${var.os_version}-sp*-v????????-hvm-ssd-x86_64"]
}
}
Which result the following,
"architecture" = "x86_64"
"hypervisor" = "xen"
"id" = "ami-0d3905203a039e3b0"
"image_id" = "ami-0d3905203a039e3b0"
But terraform is not allowing me to do this. Is there any way I can do this or I have to change the workflow?
In situations where it's not appropriate to gather all of your instances under a single resource using for_each (which would implicitly make that resource appear as a map of objects), you can get a similar result explicitly by writing a local value expression to construct an equivalent map:
locals {
amis = {
suse = data.aws_ami.suse
ubuntu = data.aws_ami.ubuntu
}
}
Then you can refer to local.amis["ubuntu"] or local.amis["suse"] (possibly replacing the element key with a variable, if you need to.
With that said, it does seem like there is a possible different approach for your case which would get there with only one data block:
locals {
os_ami_queries = {
suse = {
owners = ["amazon"]
filters = {
name = ["suse-sles-${var.os_version}-sp*-v????????-hvm-ssd-x86_64"]
}
}
ubuntu = {
owners = ["amazon"]
filters = {
name = ["ubuntu-${var.os_version}-something-something"]
}
}
}
ami_query = local.os_ami_queries[var.os_name]
}
data "aws_ami" "selected" {
owners = local.ami_query.owners
dynamic "filter" {
for_each = local.ami_query.filters
content {
name = filter.key
values = filter.value
}
}
}
This different permutation does the OS selection before the data "aws_ami" lookup, so it can use the settings associated with whichever OS was selected by the caller. The AMI id would then be in data.aws_ami.selected.id.
With that said, this approach has the disadvantage of being quite indirect and using a dynamic block, so I'd weigh that against the readability of the alternatives to pick the one which seems subjectively easiest to follow for someone who isn't familiar with this configuration. There isn't a single answer to that because to some extent it's a matter of taste, and so if you are working in a team setting this could be something to discuss with colleagues to see which approach best suits tradeoffs like how often you expect to be adding and removing supported operating systems vs. changing the details of how you use the result.
You can make it work by specifying your AMI's with a for_each and thus getting a map which you can access by key.
My data.aws_ami.myamis looks like this:
data "aws_ami" "myamis" {
for_each = toset(["suse", "ubuntu"])
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["${each.value}*"]
}
}
For test purposes I define a variable foo like this:
variable "foo" {
type = string
default = "suse"
}
Now I can access the AMI like this:
$ tf console
> data.aws_ami.myamis[var.foo].image_id
"ami-0ea50c090ba6e85c5"
You can adapt this to suit your needs for os_name and os_version.
I have solved the issue just by using conditional expression.
I am not sure if it is a standard way of doing things but it works for me.
I have tried to emulate if/elif/else with nested conditional expression.
output "name" {
value = "${ var.os_name == "ubuntu" ? data.aws_ami.ubuntu[0].image_id : (var.os_name == "redhat" ? data.aws_ami.redhat[0].image_id : (var.os_name == "centos" ? data.aws_ami.suse[0].image_id : data.aws_ami.suse[0].image_id ))}"
}

Create nested resource parameter blocks based on conditional in terraform

I am trying to create a terraform module that creates a compute instance. I want the resource to have an attached disk if and only if I have a variable attached_disk_enabled set to true during module invocation. I have this:
resource "google_compute_disk" "my-disk" {
name = "data"
type = "pd-ssd"
size = 20
count = var.attached_disks_enabled ? 1 : 0
}
resource "google_compute_instance" "computer" {
name = "computer"
boot_disk {
...
}
// How do I make this disappear if attached_disk_enabled == false?
attached_disk {
source = "${google_compute_disk.my-disk.self_link}"
device_name = "computer-disk"
mode = "READ_WRITE"
}
}
Variables have been declared for the module in vars.tf. Module invocation is like this:
module "main" {
source = "../modules/computer"
attached_disk_enabled = false
...
}
I know about dynamic blocks and how to use for loop to iterate over a list and set multiple blocks, but I'm not sure how to exclude a block from a resource using this method:
dynamic "attached-disk" {
for_each in var.disk_list
content {
source = "${google_compute_disk.my-disk.*.self_link}"
device_name = "computer-disk-${count.index}"
mode = "READ_WRITE"
}
}
I want if in place of for_each. Is there a way to do this?
$ terraform version
Terraform v0.12.0
Because your disk resource already has the conditional attached to it, you can use the result of that resource as your iterator and thus avoid specifying the conditional again:
dynamic "attached_disk" {
for_each = google_compute_disk.my-disk
content {
source = attached_disk.value.self_link
device_name = "computer-disk-${attached_disk.key}"
mode = "READ_WRITE"
}
}
To answer the general question: if you do need a conditional block, the answer is to write a conditional expression that returns either a single-item list or an empty list:
dynamic "attached_disk" {
for_each = var.attached_disk_enabled ? [google_compute_disk.my-disk[0].self_link] : []
content {
source = attached_disk.value
device_name = "computer-disk-${attached_disk.key}"
mode = "READ_WRITE"
}
}
However, in your specific situation I'd prefer the former because it describes the intent ("attach each of the disks") more directly.

Resources