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)
}
Related
I have created locally a custom module that just replicates the vault_identity_entity official module.
Here it is more or less
resource "vault_identity_entity" "this" {
name = var.name
policies = var.policies
metadata = var.metadata
disabled = var.disabled
external_policies = var.external_policies
}
In the corresponding outputs.tf file, I have this
output "entity" {
description = "The entity created"
value = vault_identity_entity.this
}
I am now trying to retrieve its id attribute as follows
module "identities_memberships" {
source = "../../../../path/to/identity_group_member_entity_ids"
for_each = {
for item in local.memberships: item.member_email => {
group = item.group
}
}
member_entity_ids = [module.identity_entities[each.key].id]
group_id = module.identity_groups[each.value.group].id
}
This fails as follows:
│ Error: Unsupported attribute
│
│ on main.tf line 101, in module "identities_memberships":
│ 101: member_entity_ids = module.identity_entities[each.key].id
│ ├────────────────
│ │ each.key is a string
│ │ module.identity_entities is a map of object
│
│ This object does not have an attribute named "id".
The vaule of local_memberships as printed when I set it in the outputs
+ local_memberships = [
+ {
+ group_name = "admins"
+ member_email = "john#gmail.com.com"
},
+ {
+ group_name = "admins"
+ member_email = "maria#gmail.com"
},
+ {
+ group_name = "operators"
+ member_email = "maria#gmail.com.com"
},
+ {
+ group_name = "viewers"
+ member_email = "adam#gmail.com.com"
},
]
and the module.identity_identities is the instantiation of the above module
module "identity_entities" {
source = "../../../../path/to/identity_entity"
for_each = local.member_groups
name = each.key
depends_on = [
module.identity_groups
]
}
where local.member_groups:
+ local_member_groups = {
+ "adam#gmail.com.com" = [
+ "viewers",
]
+ "john#gmail.com.com" = [
+ "admins",
]
+ "maria#gmail.com" = [
+ "admins",
]
+ "maria#gmail.com.com" = [
+ "operators",
]
}
Why can't I access the id attribute? What am I missing?
If I comment out the section that produces the error, the plan shows me it will create for example (among others) this:
# module.identity_entities["john#gmail.com"].vault_identity_entity.this will be created
+ resource "vault_identity_entity" "this" {
+ disabled = false
+ external_policies = false
+ id = (known after apply)
+ name = "john#gmail.com"
}
However when also hardcoding values as follows
module "identities_memberships" {
source = "../../../../modules-terraform/vault/identity_group_member_entity_ids"
for_each = {
for item in local.memberships : item.member_email => {
group = item.group
}
}
member_entity_ids = [module.identity_entities["john#gmail.com"].id]
group_id = module.identity_groups["admins"].id
depends_on = [
module.identity_entities
]
}
same error, (now for the other module as well)
│
│ on main.tf line 101, in module "identities_memberships":
│ 101: member_entity_ids = [module.identity_entities["john#gmail.com"].id]
│ ├────────────────
│ │ module.identity_entities["john#gmail.com"] is a object
│
│ This object does not have an attribute named "id".
╵
╷
│ Error: Unsupported attribute
│
│ on main.tf line 102, in module "identities_memberships":
│ 102: group_id = module.identity_groups["admins"].id
│ ├────────────────
│ │ module.identity_groups["admins"] is a object
│
To access id of the entity, you have to:
module.identity_entities["john#gmail.com"].entity.id
I am trying to create cloudwatch dashboards for ALB with multiple ALB and Target groups.
But I am getting an error while passing same value to the for loop "AWS/ApplicationELB" so is there any by which I can remove this duplicate in for loop.
data.tf
dashboards = [
{
my-dashboard-name = "Cloudwatch-Dashboard-ALB-test3"
aws-region = "us-east-1"
targets = ["app/test3/e8586bb7d49cf35b"]
target_groups = ["targetgroup/test-tg-2/f2e0260797ce83e8","targetgroup/test4/2dd3e8ae3bf2cb94"]
metrics = ["RequestCountPerTarget", "NewConnectionCount", "TargetResponseTime", "HTTPCode_Target_2XX_Count","RuleEvaluations", "HTTPCode_ELB_4XX_Count", "HTTPCode_Target_3XX_Count", "HTTP_Redirect_Count", "ActiveConnectionCount", "ProcessedBytes", "HTTPCode_ELB_3XX_Count", "RequestCount", "HTTPCode_ELB_5XX_Count", "HTTPCode_ELB_504_Count", "ConsumedLCUs", "HTTPCode_ELB_503_Count", "HTTPCode_ELB_502_Count", "HTTP_Fixed_Response_Count", "HTTPCode_Target_4XX_Count", "UnHealthyHostCount", "HealthyHostCount"]
aws-namespace = "AWS/ApplicationELB"
dim = "LoadBalancer"
service_names = ""
stat = "Average"
period = 300
},
{
my-dashboard-name = "Cloudwatch-Dashboard-ALB-test"
aws-region = "us-east-1"
targets = ["app/test2/eb397187e673ccc3"]
target_groups = ["targetgroup/test-tg/62fdb81766299e0e"]
metrics = ["RequestCountPerTarget", "NewConnectionCount", "TargetResponseTime", "HTTPCode_Target_2XX_Count","RuleEvaluations", "HTTPCode_ELB_4XX_Count", "HTTPCode_Target_3XX_Count", "HTTP_Redirect_Count", "ActiveConnectionCount", "ProcessedBytes", "HTTPCode_ELB_3XX_Count", "RequestCount", "HTTPCode_ELB_5XX_Count", "HTTPCode_ELB_504_Count", "ConsumedLCUs", "HTTPCode_ELB_503_Count", "HTTPCode_ELB_502_Count", "HTTP_Fixed_Response_Count", "HTTPCode_Target_4XX_Count", "UnHealthyHostCount", "HealthyHostCount"]
aws-namespace = "AWS/ApplicationELB"
dim = "LoadBalancer"
service_names = ""
stat = "Average"
period = 300
},
]
}
cloudwatch.tf
module "create-dashboard" {
source = "../"
for_each = { for service in local.dashboards : service.aws-namespace => service}
dashboard-name = each.value.my-dashboard-name
aws-region = each.value.aws-region
targets = each.value.targets
metrics = each.value.metrics
aws-namespace = each.value.aws-namespace
dim = each.value.dim
service_names = each.value.service_names
target_groups = each.value.target_groups
cluster_name = ["a-cluster"]
stat = each.value.stat
period = each.value.period
}
Error
╷
│ Error: Duplicate object key
│
│ on cloudwatch-v1.tf line 10, in module "create-dashboard":
│ 10: for_each = { for idx, service in local.dashboards : service.aws-namespace => service}
│ ├────────────────
│ │ service.aws-namespace is "AWS/ApplicationELB"
│
│ Two different items produced the key "AWS/ApplicationELB" in this 'for' expression. If duplicates are expected, use the ellipsis (...) after the value
│ expression to enable grouping by key
It would probably be more transparent for your config and state if the module declared name key was synced with the my-dashboard-name object key, and that would also fix your issue:
module "create-dashboard" {
source = "../"
for_each = { for service in local.dashboards : service.my-dashboard-name => service}
...
}
I'm trying to use list(string) for azure event grid event types. I used to pass all the inputs using tfvars file. and use locals to get data from it.
Below is how the tfvars looks like.
grid_configuration = {
grid1 = {
name = "testgridsiai"
rg-name = "sai-india"
location = "uksouth"
is_storage_grid = true
source_storage_account = "apmapplicationstorages"
topic_subscription = [
{
is_sink_queue = true
is_sink_eventhub = true
storage_account_name = "apmapplicationstorages"
storage_account_queue_name = "asset-data"
storage_account_queue_name_subscription_name = "store"
event_hub_name = "input"
event_hub_namespace_name = "SIAI-EH-NEW-APMS"
event_hub_subscription_name = "event-test"
event_types = ["Microsoft.Storage.BlobCreated","Microsoft.Storage.BlobDeleted"]
}
]
}
}
and below is the terraform configuration
locals {
grid_topics = { for e in var.grid_configuration : e.name => e }
subscriptions = { for hc in flatten([for h in var.grid_configuration :
[for c in h.topic_subscription : {
is_sink_queue = c.is_sink_queue
is_sink_eventhub = c.is_sink_eventhub
storage_account_name = c.storage_account_name
storage_account_queue_name = c.storage_account_queue_name
event_hub_name = c.event_hub_name
grid_name = h.name
location = h.location
rg-name = h.rg-name
storage_account_queue_name_subscription_name = c.storage_account_queue_name_subscription_name
event_hub_namespace_name = c.event_hub_namespace_name
event_hub_subscription_name = c.event_hub_subscription_name
event_types = c.event_types
}]]) : format("%s.%s.%s.%s.%s.%s.%s.%s.%s.%s.%s", hc.is_sink_queue, hc.is_sink_eventhub, hc.storage_account_name, hc.storage_account_queue_name, hc.event_hub_name, hc.grid_name, hc.rg-name, hc.location, hc.event_hub_namespace_name, hc.event_hub_subscription_name, hc.event_types) => hc }
}
resource "azurerm_eventgrid_system_topic_event_subscription" "example" {
for_each = { for k, v in local.subscriptions : k => v if v.is_sink_queue }
name = each.value.storage_account_queue_name_subscription_name
system_topic = each.value.grid_name
resource_group_name = each.value.rg-name
storage_queue_endpoint {
storage_account_id = data.azurerm_storage_account.example[each.key].id
queue_name = each.value.storage_account_queue_name
}
included_event_types = [each.value.event_types]
depends_on = [azurerm_eventgrid_system_topic.example]
}
and below is the error
│ Error: Error in function call
│
│ on event-grid/main.tf line 18, in locals:
│ 18: }]]) : format("%s.%s.%s.%s.%s.%s.%s.%s.%s.%s.%s", hc.is_sink_queue, hc.is_sink_eventhub, hc.storage_account_name, hc.storage_account_queue_name, hc.event_hub_name, hc.grid_name, hc.rg-name, hc.location, hc.event_hub_namespace_name, hc.event_hub_subscription_name, hc.event_types) => hc }
│ ├────────────────
│ │ hc.event_hub_name is "input"
│ │ hc.event_hub_namespace_name is "SIAI-EH-NEW-APMS"
│ │ hc.event_hub_subscription_name is "event-test2"
│ │ hc.event_types is list of string with 2 elements
│ │ hc.grid_name is "testgridsiai"
│ │ hc.is_sink_eventhub is true
│ │ hc.is_sink_queue is true
│ │ hc.location is "uksouth"
│ │ hc.rg-name is "sai-india"
│ │ hc.storage_account_name is "apmapplicationstorages"
│ │ hc.storage_account_queue_name is "channel-data"
│
│ Call to function "format" failed: unsupported value for "%s" at 30: string required.
I understood that I needed to use formatlist() instead of format(). Can someone throw some light on it.
Just add ...:
format("%s.%s.%s.%s.%s.%s.%s.%s.%s.%s.%s", hc.is_sink_queue, hc.is_sink_eventhub, hc.storage_account_name, hc.storage_account_queue_name, hc.event_hub_name, hc.grid_name, hc.rg-name, hc.location, hc.event_hub_namespace_name, hc.event_hub_subscription_name, hc.event_types...)
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.
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.