Reference attribute within same reserouce - terraform

Is it possible to reference an attribute within the same resource?
resource "some_resource" "foo" {
name = "cool-name"
attribute1 = "something-else-cool"
attribute2 = format("%s-%s", this.name, this.attribute1)
}

You can't do it (at least with the current terraform versions).
Instead you can use local values:
locals {
value1 = "something-else-cool"
name = "cool-name"
}
resource "some_resource" "foo" {
name = local.name
attribute1 = local.value1
attribute2 = format("%s-%s", local.name, local.value1)
}
Or simple just use input variables
resource "some_resource" "foo" {
name = var.name
attribute1 = var.someVariable
attribute2 = format("%s-%s", var.name, var.someVariable)
}
variable "name" {
type = string
default = "value"
}
variable "someVariable" {
type = string
default = "value"
}
Or, you may want to look in your provisioner's documentation if it offers data sources in case you want to fetch data from you cloud setup.
Note that you can use self in provisioner and connection blocks, for example
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
command = "echo The server's IP address is ${self.private_ip}"
}
}

Related

Terraform : Is it possible to interpolate or loop through ressources from a list within the output block?

I am trying to output gcp project information by doing something like this :
output "projects" {
value = tomap({
for project_name in ["project_1", "project_2", "project_3"] :
project_name => tomap({
id = google_project."${project_name}".id
number = google_project."${project_name}".number
})
})
description = "Projects"
}
Or like this :
output "projects" {
value = tomap({
for_each = toset([google_project.project_1,google_project.project_2])
id = each.key.id
number = each.key.number
})
description = "Projects"
}
Is it at all possible to use resource names this way? Do I have to specify every resource by duplicating code?
E.g.
output "projects" {
value = tomap({
project_1 = tomap({
id = google_project.project_1.id
number = google_project.project_1.number
})
project_2 = tomap({
id = google_project.project_2.id
number = google_project.project_2.number
})
project_3 = tomap({
id = google_project.project_3 .id
number = google_project.pproject_3 .number
})
})
description = "Projects"
}
EDIT : declared resources.
In main.tf projects 1 to 3 are declared the same way.
resource "google_project" "project_3" {
name = var.projects.project_3.name
project_id = var.projects.project_3.id
folder_id = google_folder.parent.name
billing_account = data.google_billing_account.acct.id
auto_create_network = false
}
in variables.tf
variable "projects" {
type = map(object({
name = string
id = string
}))
}
in variables.tfvars
projects = {
project_1= {
name = "project_1"
id = "project_1-12345"
}
project_2= {
name = "project_2"
id = "project_2-12345"
}
project_3= {
name = "project_2"
id = "project_2-12345"
}
}
I misunderstood your question originally. I see now that you want to reference a resource by a variable name. No you cannot do that. But your setup here doesn't really make sense, and seems more complex than it needs to be.
Consider if these options would improve your setup.
locals {
projects = { # This is equivalent to your input.
project_1 = {
name = "project_1"
id = "project_1-12345"
}
project_2 = {
name = "project_2"
id = "project_2-12345"
}
project_3 = {
name = "project_3"
id = "project_3-12345"
}
}
}
resource "google_project" "this" {
for_each = local.projects
name = each.key # or each.value.name / don't really need name
project_id = each.value.id
folder_id = google_folder.parent.name
billing_account = data.google_billing_account.acct.id
auto_create_network = false
}
output "projects_from_input" {
description = "You can of course, just use the input."
value = local.projects
}
output "projects_explicit_values" {
description = "Alternatively, if you need a subset of resource values."
value = { for k, v in google_project.this : k => {
name = v.name
id = v.project_id
} }
}
output "complete_resources" {
description = "But you can just output the complete resource."
value = google_project.this
}
I edited my initial answer after seeing the Terraform resource that creates a project. The need is a way to get a resource name in the output bloc with interpolation.
I think if a single resource is used to create all the projets instead of one resource per projet, it's easier to expose this resource in the output bloc.
For example you can configure projects metadata information from a json file, or directly a local variable or a var if needed :
Example for a json file and local variable
mymodule/resource/projects.json :
{
"projects": {
"project_1": {
"id": "project_1",
"number": "23333311"
},
"project_2": {
"id": "project_2",
"number": "33399999"
}
}
}
Then retrieve projects as a variable from locals.tf file :
mymodule/locals.tf :
locals {
projects = jsondecode(file("${path.module}/resource/projects.json"))["projects"]
}
Create your projects in a single resource with a foreach :
resource "google_project" "projects" {
for_each = local.projects
name = each.key
project_id = each.value["id"]
folder_id = google_folder.parent.name
billing_account = data.google_billing_account.acct.id
auto_create_network = false
}
Expose the projects resource in an output.tf file :
output "projects" {
value = google_project.projects
description = "Projects"
}
The same principle can be done with a var instead of local variable.

How to add multiple existing vmware tags using terraform

This is how i am currently using them:
# terraform.tfvars
vsphere_tag_name ="Name"
vsphere_tag_category ="Category"
# var.tf
variable "vsphere_tag_name" {
type = string
description = "Tag name for the vSphere virtual machines"
}
variable "vsphere_tag_category" {
type = string
description = "Tag category for the vSphere virtual machines"
}
# main.tf data
data "vsphere_tag_category" "category" {
name = "${var.vsphere_tag_category}"
}
data "vsphere_tag" "tag" {
name = "${var.vsphere_tag_name}"
category_id = "${data.vsphere_tag_category.category.id}"
}
# main.tf resource
tags = ["${data.vsphere_tag.tag.id}"]
Works perfectly fine for one tag but how to give multiple existing tags?
I'm not sure what the exact use case would be, especially as the required arguments for the tagging data sources are fairly strict, but it can be done.
Example:
# terraform.tfvars
vsphere_tag_name = ["Name1", "Name2", "Name3"]
vsphere_tag_category = "Category"
# var.tf
variable "vsphere_tag_name" {
type = list
description = "List of tag names for the vSphere virtual machines"
}
variable "vsphere_tag_category" {
type = string
description = "Tag category for the vSphere virtual machines"
}
# main.tf data
data "vsphere_tag_category" "category" {
name = "${var.vsphere_tag_category}"
}
data "vsphere_tag" "tag" {
for_each = var.vsphere_tag_name
name = "${each.value}"
category_id = "${data.vsphere_tag_category.category.id}"
}
# main.tf resource
tags = ["${data.vsphere_tag.tag[0].id}", "${data.vsphere_tag.tag[1].id}", "${data.vsphere_tag.tag[2].id}"]
Then, there's a whole other level of complexity if you have to switch vSphere Tag Category.
Appreciate the help everyone involved, here is the answer
# terraform.tfvars
tags = {
TagCategory1 = "TagName1"
TagCategory2 = "TagName2"
}
# var.tf
variable "tags" {
type = map(any)
}
variable "tag_ids" {
type = list(any)
}
# main.tf data
data "vsphere_tag_category" "category" {
count = length(var.tags)
name = keys(var.tags)[count.index]
}
data "vsphere_tag" "tag" {
count = length(var.tags)
name = var.tags[keys(var.tags)[count.index]]
category_id = data.vsphere_tag_category.category[count.index].id
}
# main.tf resource
tags = var.tag_ids:data.vsphere_tag.tag[*].id

Terraform - Output Index along with item

Is there a way to modify this output code to include the index number with the name? The current code below creates one output with Index '0', but each name should ideally be in its own Index (0, 1, 2, etc.)
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.1.0"
}
}
}
provider "azurerm" {
features {
}
}
variable "private_link_scopes" {
description = "A list of Azure private link scopes."
type = list(object({
name = string
resource_group_name = string
}))
}
locals {
rgs_map = {
for n in var.private_link_scopes : n.resource_group_name => {
name = n.resource_group_name
}
}
}
data "azurerm_resource_group" "rg" {
for_each = local.rgs_map
name = each.value.name
}
output "list" {
value = { for index, item in [var.private_link_scopes] : index => item[*].name }
}
It seems like there is a lot of structure change going on here. That may be due to other baggage or reasons that depend on it, but I think this could be simplified. I use locals in lieu of the variable, but I hope is help.
I'm not sure the splat operator there is what you want. That is the same as getting a list of all items' name attributes in that item value, but there would only be one per item so that seems strange.
locals {
scopes = [
{
name = "name_a"
rg_name = "rg_a"
},
{
name = "name_b"
rg_name = "rg_b"
}
]
}
data "azurerm_resource_group" "rg" {
# I don't see a reason to generate the intermediary map
# or to build another object in each map value. This is more
# simple. for_each with an object by name: rg_name
value = { for scope in local.scopes : scope.name => scope.rg_name }
name = each.value
}
output "list" {
# I don't see a need to use a splat expression here.
# You can just build a map with the index and the name
# here directly.
value = { for i, v in local.scopes : i => v.name }
}
In fact, if you don't need the resource group resources to be keyed by name, that can be simplified further to:
data "azurerm_resource_group" "rg" {
for_each = toset([for scope in local.scopes : scope.rg_name])
name = each.value
}

Terraform: How to set variables in a module based on a conditional?

I would like to pass a variable that will allow me to specify the list of VPC and subnet settings for an AWS instance. There are fixed VPC and subnet settings that make sense so I just want to allow a user to pick one using a single variable, i.e. use A or B.
For instance, let's say I have two available VPCs, and these are specified in a variables.tf file for a module my_instance:
variable "a_vpc_cidr_block" { default = "105.191.44.0/22" }
variable "a_vpc_id" { default = "id_a"}
variable "a_vpc_name" { default = "vpc_a" }
variable "a_subnet_availability_zone" { default = "us-east-1a" }
variable "a_subnet_cidr_block" { default = "105.191.25.0/25" }
variable "a_subnet_name" { default = "instance_A" }
variable "b_vpc_cidr_block" { default = "105.191.45.0/22" }
variable "b_vpc_id" { default = "id_b"}
variable "b_vpc_name" { default = "vpc_b" }
variable "b_subnet_availability_zone" { default = "us-east-1a" }
variable "b_subnet_cidr_block" { default = "105.191.35.0/25" }
variable "b_subnet_name" { default = "instance_B" }
The my_instance module will take a single input variable that an environment will specify, with a value of either 'A' or 'B' (is there a way to limit options for a variable to a list of values such as options=['A', 'B']?), and will be called like so in the terraform.tf for a Terraform configuration with a single instance:
module "my_instance" {
source = "../../modules/my_instance"
option = "A"
}
I want to now implement some logic within the module's main file (modules/my_instance/my_instance.tf) where it decides on which of the two collections of VPC and subnet settings it should use from the ones in modules/my_instance/variables.tf. I want to something like this (pseudocode):
if var.option == 'A'
vpc_cidr_block = var.a_vpc_cidr_block
vpc_id = var.a_vpc_id
vpc_name = var.a_vpc_name
subnet_availability_zone = var.a_subnet_availability_zone
subnet_cidr_block = var.a_subnet_cidr_block
subnet_name = var.a_subnet_name
else if var.option == 'B'
vpc_cidr_block = var.b_vpc_cidr_block
vpc_id = var.b_vpc_id
vpc_name = var.b_vpc_name
subnet_availability_zone = var.b_subnet_availability_zone
subnet_cidr_block = var.b_subnet_cidr_block
subnet_name = var.b_subnet_name
else
raise an error
# get a data resource identified by the VPC variables
data "aws_vpc" "instance_vpc" {
cidr_block = var.vpc_cidr_block
tags = {
Name = var.vpc_name
}
}
# get a data resource identified by the VPC variables
data "aws_subnet" "instance_subnet" {
vpc_id = var.vpc_id
cidr_block = var.subnet_cidr_block
availability_zone = var.subnet_availability_zone
tags = {
Name = var.subnet_name
}
}
# create an AWS key pair resource
resource "aws_key_pair" "instance_aws_key_pair" {
key_name = "component_key_${terraform.workspace}"
public_key = file("~/.ssh/terraform.pub")
}
# create the AWS EC2 instance
resource "aws_instance" "my_aws_instance" {
key_name = aws_key_pair.instance_aws_key_pair.key_name
ami = "ami-b12345"
instance_type = "t2.micro"
subnet_id = data.aws_subnet.instance_subnet.id
connection {
type = "ssh"
user = "terraform"
private_key = file("~/.ssh/terraform")
host = self.public_ip
}
tags = {
"Name" : "my_instance_name"
"Terraform" : "true"
}
}
Is this a matter of somehow using a count, something like this:
count = var.option == 'A'? 1 : 0
Is there a way to do this, or is there a better approach? I am very new to Terraform so I may be missing something obvious.
You have a couple of questions here.
Firstly, you should be able to use the newer, experimental custom validation rules to assert that a value is in a specific list of values.
Secondly, for determining which set of variables to use, I'd recommend going with a good old map in a local value.
For example,
locals {
vpc_info = {
"A" = {
vpc_cidr_block = var.a_vpc_cidr_block
vpc_id = var.a_vpc_id
vpc_name = var.a_vpc_name
subnet_availability_zone = var.a_subnet_availability_zone
subnet_cidr_block = var.a_subnet_cidr_block
subnet_name = var.a_subnet_name
}
"B" = {
vpc_cidr_block = var.b_vpc_cidr_block
vpc_id = var.b_vpc_id
vpc_name = var.b_vpc_name
subnet_availability_zone = var.b_subnet_availability_zone
subnet_cidr_block = var.b_subnet_cidr_block
subnet_name = var.b_subnet_name
}
}
}
Then you should be able to reference a specific field, within the chose option like the following
local.vpc_info[var.option].vpc_name
Let me know if this hits all your questions.

Terraform resource inheritance

Is there a way to declare an abstract resource to inherit from?
Example:
resource "digitalocean_droplet" "worker_abstract" {
abstract = true // ???
name = "swarm-worker-${count.index}"
tags = [
"${digitalocean_tag.swarm_worker.id}"
]
// other config stuff
provisioner "remote-exec" {
//...
}
}
And then use declared resource with overridden variables:
resource "worker_abstract" "worker_foo" {
count = 2
name = "swarm-worker-foo-${count.index}"
tags = [
"${digitalocean_tag.swarm_worker.id}",
"${digitalocean_tag.foo.id}"
]
}
resource "worker_abstract" "worker_bar" {
count = 5
name = "swarm-worker-bar-${count.index}"
tags = [
"${digitalocean_tag.swarm_worker.id}"
"${digitalocean_tag.bar.id}"
]
}
This might be a little more "verbose" of a solution than what you're proposing, but this sounds like a perfect use-case for modules in 0.12.
You could create a module, say in the file worker/main.tf
variable "instances" {
type = number
}
variable "name" {
type = string
}
variable "tags" {
type = list(string)
default = []
}
resource "digitalocean_droplet" "worker" {
count = var.instances
name = "swarm-worker-${var.name}-${count.index}"
tags = var.tags
// other config stuff
provisioner "remote-exec" {
//...
}
}
then you could consume your module almost exactly like your example (let's say from the directory above worker)
module "worker_foo" {
source = "./worker"
instances = 2
name = "foo"
tags = [
"${digitalocean_tag.swarm_worker.id}",
"${digitalocean_tag.foo.id}"
]
}
module "worker_bar" {
source = "./worker"
instances = 5
name = "bar"
tags = [
"${digitalocean_tag.swarm_worker.id}"
"${digitalocean_tag.bar.id}"
]
}

Resources