Why does Terraform want to create the custom attribute again? - terraform

I am try to use the Vsphere_custom_attribute resource. On the first run on an VSphere it runs fine but on the second run I get the error below.
Do you have any ideas how to solve this ?
or did i just use it wrong ?
I use this Version of Terraform and Vsphere provider.
Terraform v0.12.12
provider.template v2.1.2
provider.vsphere v1.13.0
These are the code parts where i create the custom attributes and where i use it.
resource "vsphere_custom_attribute" "hostname" {
name = "hypervisor.hostname"
managed_object_type = "VirtualMachine"
}
resource "vsphere_virtual_machine" "vm" {
...
custom_attributes = "${map(vsphere_custom_attribute.hostname.id, "${var.vsphere_name}${var.vsphere_dom}" )}"
...
}
Error :
Error: could not create custom attribute: ServerFaultCode: The name 'hypervisor.hostname' already exists.
on main.tf line 32, in resource "vsphere_custom_attribute" "hostname":
32: resource "vsphere_custom_attribute" "hostname" {
terraform plan:
I do not understand why Terraform want to create it the custom attribute as it already exists.
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# vsphere_custom_attribute.hostname will be created
+ resource "vsphere_custom_attribute" "hostname" {
+ id = (known after apply)
+ managed_object_type = "VirtualMachine"
+ name = "hypervisor.hostname"
}

This is because you're saying create the 'resource' instead of pulling the 'data' from vsphere. I had this confusion as well until understanding the 'vsphere_datastore' input required.
Try something like this (im using octopus deploy for variable replacement so ignore that im using invalid #{, should be ${ or just straight string for 0.12.x
...
data "vsphere_custom_attribute" "consul_backend_path" {
name = "consul.backend.path"
}
...
resource "vsphere_virtual_machine" "windows_virtual_machine" {
...
custom_attributes = map(data.vsphere_custom_attribute.consul_backend_path.id, "custom_attribute_value")
...
}
Keep in mind, this requires the resource of the custom attribute to be in vcenter before this virtual_machine resource is created. Otherwise you will need to do a logic test to validate if the tag is required.

Related

Issue provisioning Databricks workspace resources using Terraform

I have defined resource to provision databricks workspace on Azure using Terraform as follows which consumes the list ( of inputs from tfvar file for # of workspaces) and provision them.
resource "azurerm_databricks_workspace" "workspace" {
for_each = { for r in var.databricks_workspace_list : r.workspace_nm => r}
name = each.key
resource_group_name = each.value.resource_group_name
location = each.value.location
sku = "standard"
tags = {
Environment = "Dev"
}
}
I am trying to create additional resource as below
resource "databricks_instance_pool" "smallest_nodes" {
instance_pool_name = "Smallest Nodes"
min_idle_instances = 0
max_capacity = 300
node_type_id = data.databricks_node_type.smallest.id // data block is defined
idle_instance_autotermination_minutes = 10
}
To create instance pool, I need to pass workspace id in databricks provider block as below
provider "databricks" {
azure_client_id= *******
azure_client_secret= *******
azure_tenant_id= *******
azure_workspace_resource_id = azurerm_databricks_workspace.workspace.id
}
But when I do terraform plan, it fails with below error
Missing resource instance key
azure_workspace_resource_id = azurerm_databricks_workspace.workspace.id
Because azure_workspace_resource_id = azurerm_databricks_workspace has for_each set, its attribute must be accessed on specific instances.
For example, to correlate indices , use :
azurerm_databricks_workspace[each.key]
I couldnt use for_each in provider block, also not able to find out way to index workspace id in provider block.
Appreciate your inputs.
TF version : 0.13
Azure RM : 3.10.0
Databricks : 0.5.7
The problem is that you can create multiple workspaces when you're using for_each in the azurerm_databricks_workspace resource. But your provider block is trying to refer to a "generic" resource instance, so it's complaining.
The solution here would be either:
Remove for_each if you're creating just one workspace
instead of azurerm_databricks_workspace.workspace.id, you need to refer azurerm_databricks_workspace.workspace[<name>].id where the <name> is the specific instance of Databricks from the list of workspaces.
P.S. Your databricks_instance_pool resource doesn't have explicit depends_on, so the operation will fail with authentication error as described here.

How do I get vpc_id so I can create a VPC subnet in Terraform?

I am creating an AWS VPC with a single public subnet in a brand-new Terraform project, consisting only of a main.tf file. In that file I am using two resource blocks, aws_vpc and aws_subnet. The second resource must be attached to the first using the vpc_id attribute. The value of this attribute is created only upon apply, so it cannot be hard-coded. How do I get the ID of the resource I just created, so I can use it in the subsequent block?
resource "aws_vpc" "my_vpc" {
cidr_block = "102.0.0.0/16"
tags = {
Name = "My-VPC"
}
}
resource "aws_subnet" "my_subnet" {
vpc_id = # what goes here?
cidr_block = "102.0.0.0/24"
tags = {
Name = "My-Subnet"
}
}
The docs give the example of data.aws_subnet.selected.vpc_id for vpc_id. The value of this appears to depend on two other blocks, variable and data. I am having a hard time seeing how to wire these up to my VPC. I tried copying them directly from the docs. Upon running terraform plan I get the prompt:
var.subnet_id
Enter a value:
This is no good; I want to pull the value from the VPC I just created, not enter it at the command prompt. Where do I specify that the data source is the resource that I just created in the previous code block?
I have heard that people create a separate file to hold Terraform variables. Is that what I should to do here? It seems like it should be so basic to get an ID from one resource and use it in the next. Is there a one-liner to pass along this information?
You can just call the VPC in the subnet block by referencing Terraform's pointer. Also, doing this tells Terraform that the VPC needs to be created first and destroyed second.
resource "aws_vpc" "my_vpc" {
cidr_block = "102.0.0.0/16"
tags = {
Name = "My-VPC"
}
}
resource "aws_subnet" "my_subnet" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "102.0.0.0/24"
tags = {
Name = "My-Subnet"
}
}
I want to pull the value from the VPC I just created,
You can't do this. You can't dynamically populate variables from data sources. But you could use local instead:
locals {
subnet_id = data.aws_subnet.selected.id
}
and refer to it as local.subnet_id.

Cannot destroy one module using Terraform destroy

I have created a few instances using Terraform module:
resource "google_compute_instance" "cluster" {
count = var.num_instances
name = "redis-${format("%03d", count.index)}"
...
attached_disk {
source =
google_compute_disk.ssd[count.index].name
}
}
resource "google_compute_disk" "ssd" {
count = var.num_instances
name = "redis-ssd-${format("%03d", count.index)}"
...
zone = data.google_compute_zones.available.names[count.index % length(data.google_compute_zones.available.names)]
}
resource "google_dns_record_set" "dns" {
count = var.num_instances
name = "${var.dns_name}-${format("%03d",
count.index +)}.something.com"
...
managed_zone = XXX
rrdatas = [google_compute_instance.cluster[count.index].network_interface.0.network_ip]
}
module "test" {
source = "/modules_folder"
num_instances = 2
...
}
How can I destroy one of the instances and its dependency, say instance[1]+ssd[1]+dns[1]? I tried to destroy only one module using
terraform destroy -target module.test.google_compute_instance.cluster[1]
but it does not destroy ssd[1] and it tried to destroy both dns:
module.test.google_dns_record_set.dns[0]
module.test.google_dns_record_set.dns[1]
if I run
terraform destroy -target module.test.google_compute_disk.ssd[1]
it tried to destroy both instances and dns:
module.test.google_compute_instance.cluster[0]
module.test.google_compute_instance.cluster[1]
module.test.google_dns_record_set.dns[0]
module.test.google_dns_record_set.dns[1]
as well.
how to only destroy instance[1], ssd[1] and dns[1]? I feel my code may have some bug, maybe count.index has some problem which trigger some unexpected destroy?
I use: Terraform v0.12.29
I'm a bit confused as to why you want to terraform destroy what you'd normally want to do is decrement num_instances and then terraform apply.
If you do a terraform destroy the next terraform apply will put you right back to whatever you have configured in your terraform source.
It's a bit hard without more of your source to see what's going on- but setting num_instances on the module and using it in the module's resources feels wonky.
I would recommend you upgrade terraform and use count or for_each directly on the module rather than within it. (this was introduced in terraform 0.13.0) see https://www.hashicorp.com/blog/terraform-0-13-brings-powerful-meta-arguments-to-modular-workflows
Remove resource by resource:
terraform destroy -target RESOURCE_TYPE.NAME -target RESOURCE_TYPE2.NAME
resource "resource_type" "resource_name" {
...
}

Reference multiple aws_instance in terraform for template output

We want to deploy services into several regions.
Looks like because of the aws provider, we can't just use count or for_each, as the provider can't be interpolated. Thus I need to set this up manually:
resource "aws_instance" "app-us-west-1" {
provider = aws.us-west-1
#other stuff
}
resource "aws_instance" "app-us-east-1" {
provider = aws.us-east-1
#other stuff
}
I would like when running this to create a file which contains all the IPs created (for an ansible inventory).
I was looking at this answer:
https://stackoverflow.com/a/61788089/169252
and trying to adapt it for my case:
resource "local_file" "app-hosts" {
content = templatefile("${path.module}/templates/app_hosts.tpl",
{
hosts = aws_instance[*].public_ip
}
)
filename = "app-hosts.cfg"
}
And then setting up the template accordingly.
But this fails:
Error: Invalid reference
on app.tf line 144, in resource "local_file" "app-hosts":
122: hosts = aws_instance[*].public_ip
A reference to a resource type must be followed by at least one attribute
access, specifying the resource name
I am suspecting that I can't just reference all the aws_instance defined as above like this. Maybe to refer to all aws_instance in this file I need to use a different syntax.
Or maybe I need to use a module somehow. Can someone confirm this?
Using terraform v0.12.24
EDIT: The provider definitions use alias and it's all in the same app.tf, which I was naively assuming to be able to apply in one go with terraform apply (did I mention I am a beginner with terraform?):
provider "aws" {
alias = "us-east-1"
region = "us-east-1"
}
provider "aws" {
alias = "us-west-1"
region = "us-west-1"
}
My current workaround is to not do a join but simply listing them all individually:
{
host1 = aws_instance.app-us-west-1.public_ip
host2 = aws_instance.app-us-east-1.public_ip
# more hosts
}

How to iterate multiple resources over the same list?

New to Terraform here. I'm trying to create multiple projects (in Google Cloud) using Terraform. The problem is I've to execute multiple resources to completely set up a project. I tried count, but how can I tie multiple resources sequentially using count? Here are the following resources I need to execute per project:
Create project using resource "google_project"
Enable API service using resource "google_project_service"
Attach the service project to a host project using resource "google_compute_shared_vpc_service_project" (I'm using shared VPC)
This works if I want to create a single project. But, if I pass a list of projects as input, how can I execute all the above resources for each project in that list sequentially?
Eg.
Input
project_list=["proj-1","proj-2"]
Execute the following sequentially:
resource "google-project" for "proj-1"
resource "google_project_service" for "proj-1"
resource "google_compute_shared_vpc_service_project" for "proj-1"
resource "google-project" for "proj-2"
resource "google_project_service" for "proj-2"
resource "google_compute_shared_vpc_service_project" for "proj-2"
I'm using Terraform version 0.11 which does not support for loops
In Terraform, you can accomplish this using count and the two interpolation functions, element() and length().
First, you'll give your module an input variable:
variable "project_list" {
type = "list"
}
Then, you'll have something like:
resource "google_project" {
count = "${length(var.project_list)}"
name = "${element(var.project_list, count.index)}"
}
resource "google_project_service" {
count = "${length(var.project_list)}"
name = "${element(var.project_list, count.index)}"
}
resource "google_compute_shared_vpc_service_project" {
count = "${length(var.project_list)}"
name = "${element(var.project_list, count.index)}"
}
And of course you'll have your other configuration in those resource declarations as well.
Note that this pattern is described in Terraform Up and Running, Chapter 5, and there are other examples of using count.index in the docs here.
A small update to this question/answer (terraform 0.13 and above). The count or length is not advisable to use anymore due to the way that terraforms works, let's imagine the next scenario:
Suppose you have an array with 3 elements: project_list=["proj-1","proj-2","proj-3"], once you apply that if you want to delete the "proj-2" item from your array once you run the plan, terraform will modify your second element to "proj-3" instead of removing It from the list (more info in this good post). The solution to get the proper behavior is to use the for_each function as follow:
variable "project_list" {
type = list(string)
}
resource "google_project" {
for_each = toset(var.project_list)
name = each.value
}
resource "google_project_service" {
for_each = toset(var.project_list)
name = each.value
}
resource "google_compute_shared_vpc_service_project" {
for_each = toset(var.project_list)
name = each.value
}
Hope this helps! 👍

Resources