Reference a field within same module - terraform

Lets say I have a module block like this:
resource "aws_instance" "server" {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = var.subnet_ids
tags = {
Name = format("ami:%s", ami) # << **How to do this?**
}
}
And I have to use a module field such as ami in this example as a value to another field? Is there a way i can do this.
Above is an example in really, I am working with custom module and one value gets used multiple times so I find it non-efficient to write/change same thing at multiple places. I also want to avoid creating a separate external variable.
Is there a way above can be achieved with some sort of internal referencing of fields within same module?
TIA!

Use variables to reuse values in the same module. If you want to access other modules resourcesLets use the data attribute.
Example: start with using a module:
module "dev-server" {
source = "./modules/server"
ami = var.dev_ami_name
instance_id = var.instance_id
}
Also add a module var file with the correct variables you're passing. Then your module:
resource "aws_instance" "server" {
ami = var.ami
instance_type = var.instance_id
tags = {
Name = format("ami:%s", var.ami) # << **How to do this?**
}
}

A general answer to this is to factor out the common value into a separate named value that you can refer to elsewhere.
54m's answer shows one way to do that with input variables. That's a good choice if it's a value that should be chosen by the user of your module. If it's instead something that would make sense to have "hard-coded", but still used in multiple locations, then you can use a local value:
locals {
ami_id = "ami-a1b2c3d4"
}
resource "aws_instance" "server" {
ami = local.ami
instance_type = "t2.micro"
subnet_id = var.subnet_ids
tags = {
Name = format("ami:%s", local.ami)
}
}
A local value is private to the module that defined it, so this doesn't change the public interface of the module at all. In particular, it would not be possible for the user of this module to change the AMI ID, just like before.
If you have experience with general-purpose programming languages then it might be helpful to think of input variables as being similar to function parameters, while local values are somewhat like local variables inside a function. In this analogy, the module itself corresponds with the function.

Related

How to select the value of a specific resource in tuple based on a property value?

Hello im struggling a little to solve this challenge and hope someone can provide insights. I need to pick a specific resource out of a tuple of those resources based on a property on that resource. How would one go about achieving something like that?
Code looks something like:
resource "aws_network_interface" "network_interface" {
for_each = var.counter_of_2
// stuff
}
resource "aws_network_interface_attachment" "currently_used_eni" {
instance_id = var.instance_id
network_interface_id = <the aws_network_interface with tag.Name = "thisone">
device_index = 0
}
Because tag is not ID of resource something like that would be impossible. Imagine if you have two network interfaces with same tag. Which one should it take?
But your case might be not lost. (yet it might... depends on case)
I assume that if you want to find specific aws_network_interface then tags are unique. And if they are unique then every network interface have different tag. So you can simply use something like that:
aws_network_interface.network_interface["value_from_for_each"]
For simplicity (and assuming that one network interface = one tag) I'll make this for_each collection to be set of tag names.
variable "interface_tags" {
type = set(string)
default = ["tag1", "tag2", "thisone"]
}
resource "aws_network_interface" "network_interface" {
for_each = var.interface_tags
// stuff
}
resource "aws_network_interface_attachment" "currently_used_eni" {
instance_id = var.instance_id
network_interface_id = aws_network_interface.network_interface["thisone"].id
device_index = 0
}
And this should work (and I hope it can be used in your case)

How to re-use a terraform module in multiple configurations?

I have a terraform plan that defines most of my BQ environment.
I'm working on a cross-region deployment which will replicate some of my tables to multiple regions.
Rather than copy pasting the same module in every place that I need it at, I'd like to define the module in one place and just call that on every configuration that needs it.
Example I have the following file structure
./cross_region_tables
-> tables.tf
./foo
-> tables.tf
./bar
-> tables.tf
I'd like to define some_module in ./cross_region_tables/tables.tf like so
output "some_module" {
x = something
region = var.region
}
Then I'd like just call some_module from ./foo/tables.tf
The problem is that I don't know how to call this specific module, since ./cross_region_tables/tables.tf will contain several table definitions (as output objects). I know how to import a child module, but I don't know how to call a specific output within that child module
I've solved the issue by adding a module object to the child module with a variable for the region, then calling the child from each regional configuration and passing the region as a variable.
in child folder main.tf:
variable "region" = {}
module "foo" {
x = "something"
y = "something_else"
region = var.region
}
in regional folder for regionX
variable "region" = {
default = regionX
}
module "child" {
source = "../path/to/child"
region = var.region
}
in regional folder for regionY
variable "region" = {
default = regionY
}
module "child" {
source = "../path/to/child"
region = var.region
}
repeat for as many regions as necessary.
You can pass the provider to your modules and each provider with a different
region...
That is well documented here:
https://www.terraform.io/language/modules/develop/providers#passing-providers-explicitly
# The default "aws" configuration is used for AWS resources in the root
# module where no explicit provider instance is selected.
provider "aws" {
region = "us-west-1"
}
# An alternate configuration is also defined for a different
# region, using the alias "usw2".
provider "aws" {
alias = "usw2"
region = "us-west-2"
}
# An example child module is instantiated with the alternate configuration,
# so any AWS resources it defines will use the us-west-2 region.
module "example" {
source = "./example"
providers = {
aws = aws.usw2
}
}
The other part is what you mentioned:
The problem is that I don't know how to call this specific module, since ./cross_region_tables/tables.tf will contain several table definitions
Resources within that module (cross_region_tables) can be turned off/on with variables

Terraform & OpenStack - Zero downtime flavor change

I’m using openstack_compute_instance_v2 to create instances in OpenStack. There is a lifecycle setting create_before_destroy = true present. And it works just fine in case I e.g. change volume size, where instances needs to be replaced.
But. When I do flavor change, which can be done by using resize instance option from OpenStack, it does just that, but doesn’t care about any HA. All instances in the cluster are unavailable for 20-30 seconds, before resize finishes.
How can I change this behaviour?
Some setting like serial from Ansible, or some other options would come in handy. But I can’t find anything.
Just any solution that would allow me to say “at least half of the instances needs to be online at all times”.
Terraform version: 12.20.
TF plan: https://pastebin.com/ECfWYYX3
The Openstack Terraform provider knows that it can update the flavor by using a resize API call instead of having to destroy the instance and recreate it.
Unfortunately there's not currently a lifecycle option that forces mutable things to do a destroy/create or create/destroy when coupled with the create_before_destroy lifecycle customisation so you can't easily force this to replace the instance instead.
One option in these circumstances is to find a parameter that can't be modified in place (these are noted by the ForceNew flag on the schema in the underlying provider source code for the resource) and then have a change in the mutable parameter also cascade a change to the immutable parameter.
A common example here would be replacing an AWS autoscaling group when the launch template (which is mutable compared to the immutable launch configurations) changes so you can immediately roll out the changes instead of waiting for the ASG to slowly replace the instances over time. A simple example would look something like this:
variable "ami_id" {
default = "ami-123456"
}
resource "random_pet" "ami_random_name" {
keepers = {
# Generate a new pet name each time we switch to a new AMI id
ami_id = var.ami_id
}
}
resource "aws_launch_template" "example" {
name_prefix = "example-"
image_id = var.ami_id
instance_type = "t2.small"
vpc_security_group_ids = ["sg-123456"]
}
resource "aws_autoscaling_group" "example" {
name = "${aws_launch_template.example.name}-${random_pet.ami_random_name.id}"
vpc_zone_identifier = ["subnet-123456"]
min_size = 1
max_size = 3
launch_template {
id = aws_launch_template.example.id
version = "$Latest"
}
lifecycle {
create_before_destroy = true
}
}
In the above example a change to the AMI triggers a new random pet name which changes the ASG name which is an immutable field so this triggers replacing the ASG. Because the ASG has the create_before_destroy lifecycle customisation then it will create a new ASG, wait for the minimum amount of instances to pass EC2 health checks and then destroy the old ASG.
For your case you can also use the name parameter on the openstack_compute_instance_v2 resource as that is an immutable field as well. So a basic example might look like this:
variable "flavor_name" {
default = "FLAVOR_1"
}
resource "random_pet" "flavor_random_name" {
keepers = {
# Generate a new pet name each time we switch to a new flavor
flavor_name = var.flavor_name
}
}
resource "openstack_compute_instance_v2" "example" {
name = "example-${random_pet.flavor_random_name}"
image_id = "ad091b52-742f-469e-8f3c-fd81cadf0743"
flavor_name = var.flavor_name
key_pair = "my_key_pair_name"
security_groups = ["default"]
metadata = {
this = "that"
}
network {
name = "my_network"
}
}
So. At first I've started digging how, as #ydaetskcoR proposed, to use random instance name.
Name wasn't an option, both because in openstack it is a mutable parameter, and because I have a decided naming schema which I can't change.
I've started to look for other parameters that I could modify to force instance being created instead of modified. I've found about personality.
https://www.terraform.io/docs/providers/openstack/r/compute_instance_v2.html#instance-with-personality
But it didn't work either. Mainly, because personality is no longer supported as it seems:
The use of personality files is deprecated starting with the 2.57 microversion. Use metadata and user_data to customize a server instance.
https://docs.openstack.org/api-ref/compute/
Not sure if terraform doesn't support it, or there are any other issues. But I went with user_data. I've already used user_data in compute instance module, so adding some flavor data there shouldn't be an issue.
So, within user_data I've added the following:
user_data = "runcmd:\n - echo ${var.host["flavor"]} > /tmp/tf_flavor"
No need for random pet names, no need to change instances names. Just change their "personality" by adding flavor name somewhere. This does force instance to be recreated when flavor changes.
So. Instead of simply:
# module.instance.openstack_compute_instance_v2.server[0] will be updated in-place
~ resource "openstack_compute_instance_v2" "server" {
I have now:
-/+ destroy and then create replacement
+/- create replacement and then destroy
Terraform will perform the following actions:
# module.instance.openstack_compute_instance_v2.server[0] must be replaced
+/- resource "openstack_compute_instance_v2" "server" {

How to create multiple resources in a for loop with terraform?

I have looked at several bits of documentation as well as a udemy course on terraform and I do not understand how to do the thing that I want to do. I want to create a for loop and in it I want to create an S3 event notification, create an Sns topic that listens to that notification, create an Sqs queue and then subscribe the queue to the sns topic. It seems like for loops in terraform are not advanced enough to do this. Am I wrong, is there any documentation or examples that explain how to use for loops for this use case?
Thanks in advance.
An example to create AWS VPC subnets then give them to AWS EC2 instances.
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidr_blocks)
vpc_id = var.vpc_id
cidr_block = var.public_subnet_cidr_blocks[count.index]
}
resource "aws_instance" "public_ec2" {
count = length(var.public_subnet_ids)
subnet_id = var.public_subnet_ids[count.index]
ami = var.ami_id
instance_type = "t2.micro"
tags = {
Name = "PublicEC2${count.index}}"
}
provisioner "local-exec" {
command = <<EOF
echo "Public EC2 ${count.index} ID is ${self.id}"
EOF
}
}
There is no syntax like below to create resources.
[ for name in var.names:
aws_s3_bucket {...}
aws_sns_topic {...}
]
For expression is basically for values, not for creating resources.
for Expressions
A for expression creates a complex type value by transforming another complex type value.
To create multiple resources, as below in the document use for_each or count.
for_each: Multiple Resource Instances Defined By a Map, or Set of Strings
By default, a resource block configures one real infrastructure object. However, sometimes you want to manage several similar objects, such as a fixed pool of compute instances. Terraform has two ways to do this: count and for_each.
The for_each example looks like the following:
variable names {
type = list(string)
description = "List of names"
}
resoure "aws_foo" "bar" {
for_each = toset(var.names)
name = each.key
}
This will create N resources (length of names), using the each.key to specify the value to assign to name each time

Iterate over map with lists in terraform 0.12

I am using terraform 0.12.8 and I am trying to write a resource which would iterate over the following variable structure:
variable "applications" {
type = map(string)
default = {
"app1" = "test,dev,prod"
"app2" = "dev,prod"
}
}
My resource:
resource "aws_iam_user" "custom" {
for_each = var.applications
name = "circleci-${var.tags["ServiceType"]}-user-${var.tags["Environment"]}-${each.key}"
path = "/"
}
So, I can iterate over my map. However, I can't figure out how to verify that var.tags["Environment"] is enabled for specific app e.g. app1.
Basically, I want to ensure that the resource is created for each application as long as the Environment variable is in the list referencing app name in the applications map.
Could someone help me out here?
Please note that I am happy to go with a different variable structure if you have something to propose that would accomplish my goal.

Resources