Reference to undeclared resource - terraform

Azure Loadbalancer Rule Module code is like below:
main.tf.
resource "azurerm_lb_rule" "lb_rule" {
count = length(var.lb_rule_specs)
name = var.lb_rule_specs[count.index]["name"]
resource_group_name = var.resource_group_name
loadbalancer_id = var.loadbalancer_id
frontend_ip_configuration_name = var.lb_rule_specs[count.index]["frontend_ip_configuration_name"]
protocol = var.lb_rule_specs[count.index]["protocol"]
frontend_port = var.lb_rule_specs[count.index]["frontend_port"]
backend_port = var.lb_rule_specs[count.index]["backend_port"]
probe_id = var.probe_id
load_distribution = var.load_distribution
backend_address_pool_id = var.backend_address_pool_id
}
variables.tf
variable "lb_rule_specs" {
description = "Load balancer rules specifications"
type = list(map(string))
}
variable "resource_group_name" {
description = "Name of the resource group"
type = string
}
variable "loadbalancer_id" {
description = "ID of the load balancer"
type = string
}
variable "backend_address_pool_id" {
description = "Backend address pool id for the load balancer"
type = string
}
variable "probe_id" {
description = "ID of the loadbalancer probe"
type = string
default = ""
}
variable "load_distribution" {
description = "Specifies the load balancing distribution type to be used by the Load Balancer. Possible values are: Default – The load balancer is configured to use a 5 tuple hash to map traffic to available servers. SourceIP – The load balancer is configured to use a 2 tuple hash to map traffic to available servers. SourceIPProtocol – The load balancer is configured to use a 3 tuple hash to map traffic to available servers. Also known as Session Persistence, where the options are called None, Client IP and Client IP and Protocol respectively."
type = string
default = ""
}
Calling module as below:
variable "loadbalancer_rule" {
description = "Map of loadbalancer-rule objects"
type = any
default = null
}
module "loadbalancer_rule" {
for_each = coalesce(var.loadbalancer_rule, {})
source = "company.com.au/tfmodules/loadbalancer-rule/azurerm"
version = "7.0.0-2-1.0"
backend_address_pool_id = try(each.value.backend_address_pool_id, null)
load_distribution = try(each.value.load_distribution, "")
loadbalancer_id = each.value.loadbalancer_ref != null ? module.loadbalancer[each.value.loadbalancer_ref].id : null
probe_id = each.value.probe_ref != null ? module.loadbalancer_probe[each.value.probe_ref].id : null
resource_group_name = var.__ngc.environment_resource_groups
lb_rule_specs = [
for lb_rule_spec in each.value.lb_rule_specs :
{
frontend_ip_configuration_name = try(for_each.lb_rule_spec.frontend_ip_configuration_name, null)
protocol = try(for_each.lb_rule_spec.protocol, null)
frontend_port = try(for_each.lb_rule_spec.frontend_port, null)
backend_port = try(for_each.lb_rule_spec.backend_port, null)
}
]
}
lbrule.auto.tfvars.json file like below:
{
"loadbalancer_rule": {
"patterns_default_loadbalancer_rule": {
"backend_address_pool_id": null,
"lb_rule_specs" : {
"name" : "test2",
"protocol": "tcp",
"frontend_port": "8080",
"backend_port": "8081",
"frontend_ip_configuration_name": "LBFrontendIPConfig_1"
},
"name" : "test2",
"protocol": "tcp",
"frontend_port": "8100",
"backend_port": "9100",
"frontend_ip_configuration_name": "LBFrontendIPConfig_2"
},
"load_distribution": "",
"loadbalancer_ref": "patterns_default_loadbalancer",
"probe_ref": "patterns_default_loadbalancer_probe"
}
}
Unfortunately, I get error as like below:
│ Error: Reference to undeclared resource
│
│ on loadbalancer_rule.tf line 20, in module "loadbalancer_rule":
│ 20: frontend_ip_configuration_name = try(for_each.lb_rule_spec.frontend_ip_configuration_name, null)
│
│ A managed resource "for_each" "lb_rule_spec" has not been declared in the
│ root module.
╵
╷
│ Error: Reference to undeclared resource
│
│ on loadbalancer_rule.tf line 21, in module "loadbalancer_rule":
│ 21: protocol = try(for_each.lb_rule_spec.protocol, null)
│
│ A managed resource "for_each" "lb_rule_spec" has not been declared in the
│ root module.
╵
╷
│ Error: Reference to undeclared resource
│
│ on loadbalancer_rule.tf line 22, in module "loadbalancer_rule":
│ 22: frontend_port = try(for_each.lb_rule_spec.frontend_port, null)
│
│ A managed resource "for_each" "lb_rule_spec" has not been declared in the
│ root module.
╵
╷
│ Error: Reference to undeclared resource
│
│ on loadbalancer_rule.tf line 23, in module "loadbalancer_rule":
│ 23: backend_port = try(for_each.lb_rule_spec.backend_port, null)
│
│ A managed resource "for_each" "lb_rule_spec" has not been declared in the
│ root module.
I am guessing that I am not writing outer OR inner loop properly? or perhaps the definition file (variable file) is not right?
There could be one or more lb rules and for each of those there could be 1 or more front end ip, protocol, front end port and backend port.

Related

Setting a static IP Address to an Virtual Machine from OVF Template

I'm using Terraform's vSphere Provider. I have a local OVF template that I'm trying to deploy a VM from. I wish to set a static IP address for the soon-to-be-deployed VM, but Terraform keeps complaining
"Argument named ipv4_address is not expected here."
"Argument named ipv4_netmask is not expected here."
"Argument named "ipv4_gateway` is not expected here."
DHCP is not an option so I have to set it manually. Terraform gives an option to allocate IPs manually, so what can I do?
This is the Template I'm using:
data "vsphere_ovf_vm_template" "ovfRemote" {
name = "foo"
disk_provisioning = "thick"
datastore_id = data.vsphere_datastore.datastore.id
host_system_id = data.vsphere_host.host.id
resource_pool_id = data.vsphere_resource_pool.default.id
local_ovf_path = "/path/to/ovf"
ip_protocol = "IPV4"
ip_allocation_policy = "STATIC_MANUAL"
ovf_network_map = {
"Network" : data.vsphere_network.network.id
}
}
and here's the Virtual Machine's I'm trying to create:
resource "vsphere_virtual_machine" "vmFromLocalOvf" {
name = "terraform-using-ovf-template"
datacenter_id = data.vsphere_datacenter.datacenter.id
datastore_id = data.vsphere_datastore.datastore.id
host_system_id = data.vsphere_host.host.id
resource_pool_id = data.vsphere_resource_pool.default.id
wait_for_guest_net_timeout = 0
wait_for_guest_ip_timeout = 0
num_cpus = data.vsphere_ovf_vm_template.ovfRemote.num_cpus
memory = data.vsphere_ovf_vm_template.ovfRemote.memory
guest_id = data.vsphere_ovf_vm_template.ovfRemote.guest_id
ovf_deploy {
allow_unverified_ssl_cert = false
enable_hidden_properties = false
local_ovf_path = data.vsphere_ovf_vm_template.ovfRemote.local_ovf_path
disk_provisioning = data.vsphere_ovf_vm_template.ovfRemote.disk_provisioning
ip_protocol = data.vsphere_ovf_vm_template.ovfRemote.ip_protocol
ip_allocation_policy = data.vsphere_ovf_vm_template.ovfRemote.ip_allocation_policy
ovf_network_map = data.vsphere_ovf_vm_template.ovfRemote.ovf_network_map
}
#### HERE IS THE PROBLEM I'M FACING ###
# With the provider 'vsphere_virtual_machine' I should be able to specify
# a network interface. Specifically, I'm interested in setting a
# * ipv4_address
# * ipv4_netmask
# * ipv4_gateway
network_interface {
ipv4_address = "11.22.33.44"
ipv4_netmask = 24
}
ipv4_gateway = "55.66.77.88"
Errors:
$ terraform plan
╷
│ Error: Unsupported argument
│
│ on main.tf line 82, in resource "vsphere_virtual_machine" "vmFromLocalOvf":
│ 82: ipv4_address = "10.112.128.22"
│
│ An argument named "ipv4_address" is not expected here.
╵
╷
│ Error: Unsupported argument
│
│ on main.tf line 83, in resource "vsphere_virtual_machine" "vmFromLocalOvf":
│ 83: ipv4_netmask = 24
│
│ An argument named "ipv4_netmask" is not expected here.
╵
╷
│ Error: Unsupported argument
│
│ on main.tf line 86, in resource "vsphere_virtual_machine" "vmFromLocalOvf":
│ 86: ipv4_gateway = "10.112.128.1"
│
│ An argument named "ipv4_gateway" is not expected here.

Terraform dynamicly attach disks and network when vps is counted

I can't figure out how to dynamically connect disks and networks to virtual machines if they are counted
I expect i will pass variables like this:
module "vps-test" {
source = "../module"
count = 3
server_name = "vpstest"
server_image = "debian-11"
server_type = "cx21"
server_datacenter = "fsn1-dc14"
labels = { groups = "test_offline.test_vps" }
server_network = {
backend_network = {
subnet_id = (data.terraform_remote_state.htz_network.outputs.main-subnet-id)
ip = "" #DHCP
}
custom_network = {
subnet_id = "1867414"
ip = ""
}
}
volumes = {
firts_volume = {
name = "volume1"
size = "20"
}
second_volume = {
name = "volume1"
size = "20"
}
}
hetzner_cloud_token = var.hetzner_cloud_offline_main_api_token
cloud_init_file_path = "../module/scripts/user.yaml"
}
and the module will create 3 identical VMs, where each will have 2 disks and 2 networks
It's Hetznere cloud provider, here is my simple code:
resource "hcloud_server" "vps" {
count = var.server_count
name = var.server_count > 1 ? "${var.server_name}-${count.index}" : var.server_name
image = var.server_image
server_type = var.server_type
datacenter = var.server_datacenter
user_data = data.template_file.ansible_user_data.rendered
labels = var.labels
}
resource "hcloud_volume" "volume" {
for_each = var.volumes
name = tostring(each.value["name"])
size = tonumber(each.value["size"])
server_id = hcloud_server.vps.id
automount = true
format = var.volume_filesystem
}
resource "hcloud_server_network" "network" {
for_each = var.server_network
server_id = hcloud_server.vps.id
subnet_id = each.value["subnet_id"]
ip = tostring(each.value["ip"])
}
Errors:
│ Error: Missing resource instance key
│
│ on ../module/resource.tf line 15, in resource "hcloud_volume" "volume":
│ 15: server_id = hcloud_server.vps.id
│
│ Because hcloud_server.vps has "count" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│ hcloud_server.vps[count.index]
╵
╷
│ Error: Missing resource instance key
│
│ on ../module/resource.tf line 22, in resource "hcloud_server_network" "network":
│ 22: server_id = hcloud_server.vps.id
│
│ Because hcloud_server.vps has "count" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│ hcloud_server.vps[count.index]
if using recommends from error log
│ Error: Reference to "count" in non-counted context
│
│ on ../module/resource.tf line 15, in resource "hcloud_volume" "volume":
│ 15: server_id = hcloud_server.vps[count.index].id
│
│ The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.
╵
╷
│ Error: Reference to "count" in non-counted context
│
│ on ../module/resource.tf line 22, in resource "hcloud_server_network" "network":
│ 22: server_id = hcloud_server.vps[count.index].id
│
│ The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.
but if server_id = hcloud_server.vps[0].id (or any specific index) - working
where is the correct way
Since you are using both count and for_each you need to iterate over both of them. Basically it means you need a double for loop. One way to do it in TF is with the help of setproduct:
resource "hcloud_volume" "volume" {
for_each = {for idx, val in setproduct(range(var.server_count), keys(var.volumes)): idx => val}
name = tostring(var.volume[each.value[1]].name)
size = tonumber(var.volume[each.value[1]].size)
server_id = hcloud_server.vps[each.value[0]].id
automount = true
format = var.volume_filesystem
}
resource "hcloud_server_network" "network" {
for_each = {for idx, val in setproduct(range(var.server_count), keys(var.server_network)): idx => val}
server_id = hcloud_server.vps[each.value[0]].id
subnet_id = var.server_network[each.value[1]].subnet_id
ip = tostring(var.server_network[each.value[1]].ip)
}

Trouble referring type list(object) variable from module directory using for_each into main dir

I have created a variable of type list(object) on a module directory. My goal is to call this variable under a different directory using a for_each loop. (Tree attached below)
.
├── main.tf
├── output.tf
└── proxmox-instance
├── main.tf
├── output.tf
├── providers.tf
└── variables.tf
The variable.tf file looks like this
variable "forwarded_ports" {
description = "Your Template name - Will be cloned from this template"
type = list(object({
host_port = number
guest_port = number
}))
default = [{
"host_port" = 2222,
"guest_port" = 22
}]
}
While the ./proxmox-instance/main.tf looks like this
resource "iptables_nat" "dnat-front" {
# Creates an entry for each template specified
for_each = [for k in var.forwarded_ports : k]
name = "iptable-${var.vm_name}-${each.value.host_port}-to-${each.value.guest_port}"
on_cidr_blocks = ["195.201.172.34"] # Proxmox network device IP here
dnat {
iface = "enp7s0"
protocol = "tcp"
to_port = each.value.host_port # Custom Port for ingress connections
nat_ip = "${proxmox_vm_qemu.vm.default_ipv4_address}:${each.value.guest_port}" # VM Internal IP here
}
}
The issue occurs while trying to refer the code at the forwarded_ports section below
module "instance" {
source = "./proxmox-instance"
vm_name = "Terraform"
template_name = "ubuntu-20.04"
# cloud-init settings
ipconfig0 = "ip=dhcp"
cloud_init_user = "ubuntu"
cloud_init_password = "password"
forwarded_ports = [
{
host_port = 2222
guest_port = 22
},
{
host_port = 5555
guest_port = 22
},
]
}
I've tried playing with the syntax abit and got different results
Output 1/2
for_each = [for k in var.forwarded_ports : k]
│ Error: Invalid for_each argument
│
│ on proxmox-instance\main.tf line 37, in resource "iptables_nat" "dnat-front":
│ 37: for_each = [for k in var.forwarded_ports : k]
│ ├────────────────
│ │ var.forwarded_ports is list of object with 2 elements
│
│ The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type tuple.
Output 2/2
for_each = toset([for k in var.forwarded_ports : k])
│ Error: Invalid for_each set argument
│
│ on proxmox-instance\main.tf line 37, in resource "iptables_nat" "dnat-front":
│ 37: for_each = toset([for k in var.forwarded_ports : k])
│ ├────────────────
│ │ var.forwarded_ports is list of object with 2 elements
│
│ The given "for_each" argument value is unsuitable: "for_each" supports maps and sets of strings, but you have provided a set containing type object.
You are providing a variable of type list(objects) and you are trying to use a list with for_each (which error output shows you it is not possible) while it can work only with maps or sets. What you could try is the following:
resource "iptables_nat" "dnat-front" {
# Creates an entry for each template specified
for_each = { for i in var.forwarded_ports : i.host_port => {
host_port = i.host_port
guest_port = i.guest_port
} }
name = "iptable-${var.vm_name}-${each.value.host_port}-to-${each.value.guest_port}"
on_cidr_blocks = ["195.201.172.34"] # Proxmox network device IP here
dnat {
iface = "enp7s0"
protocol = "tcp"
to_port = each.value.host_port # Custom Port for ingress connections
nat_ip = "${proxmox_vm_qemu.vm.default_ipv4_address}:${each.value.guest_port}" # VM Internal IP here
}
}
Alternatively, you could define a locals block and create the map you want there:
locals {
fowarded_ports = { for i in local.forwarded_ports : i.host_port => {
host_port = i.host_port
guest_port = i.guest_port
} }
}
And then in the resource block:
resource "iptables_nat" "dnat-front" {
# Creates an entry for each template specified
for_each = local.forwarded_ports
name = "iptable-${var.vm_name}-${each.value.host_port}-to-${each.value.guest_port}"
on_cidr_blocks = ["195.201.172.34"] # Proxmox network device IP here
dnat {
iface = "enp7s0"
protocol = "tcp"
to_port = each.value.host_port # Custom Port for ingress connections
nat_ip = "${proxmox_vm_qemu.vm.default_ipv4_address}:${each.value.guest_port}" # VM Internal IP here
}
}
forwarded_ports is a list. for_each works with maps. You can easly convert list to map as follows:
for_each = { for i,v in var.forwarded_ports : i => v}

Terraform keep saying variable not defined though its defined

I am trying to create a resource in azure via Terraform. Even though I have declared all required variables, its erroring out. I am using modules for the same...
Here is the below code that I tried:
My Module's Bastion/main.tf that used to create resource:
resource "azurerm_public_ip" "syn_pip" {
name = "pip-${var.prefix}-${var.postfix}"
location = var.location
resource_group_name = var.rg_name
allocation_method = var.bastion_allocation_method
sku = var.bastion_sku_type
}
My modules's Bastion/variables.tf :
variable "bastion_allocation_method" {
type = string
description = "allocation method for bastion"
}
variable "bastion_sku_type" {
type = string
description = "sku to be used for bastion"
}
My root module bastion.tf :
module "bastion" {
source = "../modules/bastion"
rg_name = module.resource_group.name
location = module.resource_group.location
allocation_method = var.bastion_allocation_method
sku = var.bastion_sku_type
prefix = var.prefix
postfix = random_string.postfix.result
subnet_id = azurerm_subnet.bastion_subnet.id
}
root module's variable.tf:
variable "bastion_allocation_method" {
type = string
}
variable "bastion_sku_type" {
type = string
}
my terraform.tfvars which passed to terraform plan :
"bastion_allocation_method": "Static",
"bastion_sku_type": "Standard"
Error that I get:
│ Error: Missing required argument
│
│ on bastion.tf line 1, in module "bastion":
│ 1: module "bastion" {
│
│ The argument "bastion_allocation_method" is required, but no definition was found.
╵
╷
│ Error: Missing required argument
│
│ on bastion.tf line 1, in module "bastion":
│ 1: module "bastion" {
│
│ The argument "bastion_sku_type" is required, but no definition was found.
╵
╷
│ Error: Unsupported argument
│
│ on bastion.tf line 7, in module "bastion":
│ 7: allocation_method = var.bastion_allocation_method
│
│ An argument named "allocation_method" is not expected here.
╵
╷
│ Error: Unsupported argument
│
│ on bastion.tf line 8, in module "bastion":
│ 8: sku = var.bastion_sku_type
│
│ An argument named "sku" is not expected here.
╵
Exited with code exit status 1
Can someone suggest, what is the mistake I am doing ?
In your Bastion module in the variables.tf you specify the arguments
variable "bastion_allocation_method" {
...
}
variable "bastion_sku_type" {
...
}
In your case your module expects the vars bastion_allocation_method and bastion_sku_type
But when you call the module, you dont pass the variable names properly
module "bastion" {
source = "../modules/bastion"
...
allocation_method = var.bastion_allocation_method
sku = var.bastion_sku_type
...
}
Use instead
module "bastion" {
source = "../modules/bastion"
...
bastion_allocation_method = var.bastion_allocation_method
bastion_sku_type = var.bastion_sku_type
...
}
The variable assignments that you pass in module clause should match the definitions in the module's variables.tf

Terraform for_each map of objects

Curently I'm trying to build dev and production environment without duplicating resource blocks. I have found that I can crate map of objects and use for loop for this.
For this I have created this piece of code that was
variable "sqlserver" {
type = map(object({
name = string
username = string
password = string
}))
}
sqlserver = {
"dev" = {
name = "devsonovasqlserver"
username = "dev_username"
password = "biaJB8wQJb4n!RwG"
}
"prd" = {
name = "testexamplesqlsonova"
username = "prd_username"
password = "biaJB8wQJb4asdan!RwG"
}
}
resource "azurerm_sql_server" "sql_server" {
for_each = var.sqlserver
name = each.value["name"]
resource_group_name = var.dev_main_rg
location = var.location
version = "12.0"
administrator_login = each.value["username"]
administrator_login_password = each.value["password"]
}
This sadly raise Error like
╷
│ Error: Incorrect attribute value type
│
│ on main.tf line 56, in resource "azurerm_sql_server" "dev_sql_server":
│ 56: name = var.sqlserver.name
│ ├────────────────
│ │ var.sqlserver.name is a object, known only after apply
│
│ Inappropriate value for attribute "name": string required.
╵
Your code is valid. When I copy it to a project of my own it works fine. I guess you have something else in your files that make it work different from what is shown here.

Resources