Passing Terraform variables between Modules within Modules? - terraform

I have an issue I have gotten stuck on building a module from the Kubespray project.
I have the following folder structure:
terraform/
modules/
kubespray/
modules/
compute/
ips/
network/
main.tf
variables.tf
roles/
kubespray/
terraform_setup/
main.tf
Within the kubespray/main.tf module there are variables being passed around:
module "ips" {
source = "./modules/ips"
number_of_k8s_masters = "${var.number_of_k8s_masters}"
number_of_k8s_masters_no_etcd = "${var.number_of_k8s_masters_no_etcd}"
number_of_k8s_nodes = "${var.number_of_k8s_nodes}"
floatingip_pool = "${var.floatingip_pool}"
number_of_bastions = "${var.number_of_bastions}"
external_net = "${var.external_net}"
network_name = "${var.network_name}"
router_id = "${module.network.router_id}"
}
module "compute" {
source = "./modules/compute"
...
k8s_master_fips = "${module.ips.k8s_master_fips}"
k8s_master_no_etcd_fips = "${module.ips.k8s_master_no_etcd_fips}"
k8s_node_fips = "${module.ips.k8s_node_fips}"
bastion_fips = "${module.ips.bastion_fips}"
...
}
output "private_subnet_id" {
value = "${module.network.subnet_id}"
}
output "floating_network_id" {
value = "${var.external_net}"
}
output "router_id" {
value = "${module.network.router_id}"
}
output "k8s_master_fips" {
value = "${concat(module.ips.k8s_master_fips, module.ips.k8s_master_no_etcd_fips)}"
}
output "k8s_node_fips" {
value = "${module.ips.k8s_node_fips}"
}
output "bastion_fips" {
value = "${module.ips.bastion_fips}"
}
If I run my terraform init/apply from inside the submodule in modules/kubespray/ It works fine
If I run from my role/kubespray that has
module "kubespray" {
source = "../../../modules/kubespray"
providers = {
openstack.src = openstack
}
}
it fails with
Error: Invalid index
on ../../../modules/kubespray/modules/compute/main.tf line 670, in
resource "openstack_compute_floatingip_associate_v2" "k8s_node": 670:
floating_ip = "${var.k8s_node_fips[count.index]}"
|----------------
| count.index is 0
| var.k8s_node_fips is empty list of dynamic
Help would be extremely appreicated

When you run terraform, the directory becomes an implicit "root module". As it's explained in the Input Variables documentation, root modules can access TF inputs from the CLI or environment, but all other called modules need to have these passed in.
I'm assuming what's happening could be that your ~kubespray/variables.tf input variables have default values assigned which are taking effect. If that's the case, then these should be copied over to ~terraform_setup/variables.tf and passed into the calling module:
module "kubespray" {
source = "../../../modules/kubespray"
providers = {
openstack.src = openstack
}
}

Without asking for further information, my guess here is that you have an empty array being passed in (${var.k8s_node_fips[count.index]}) with a value greater than 0 for var.node_root_volume_size_in_gb and var.number_of_k8s_nodes_no_floating_ip, causing an error when trying to index an empty array for a string that the resource declaration requires.
Error: Invalid index
on ../../../modules/kubespray/modules/compute/main.tf line 670, in resource "openstack_compute_floatingip_associate_v2" "k8s_node": 670: floating_ip = "${var.k8s_node_fips[count.index]}" |---------------- | count.index is 0 | var.k8s_node_fips is empty list of dynamic
According to the error, it seems this may be a interpolation error at ${var.k8s_node_fips[count.index]}.
What it seems like is happening is that the variable is empty, but the count is set to a non-zero number, thereby causing TF to error out.
Looking at the original project code, seems like the count variable depends on var.node_root_volume_size_in_gb to be 0 to be set to 0; otherwise will set the count to the value of var.number_of_k8s_nodes_no_floating_ip.
Snippet that the error seems to be occuring at:
resource "openstack_compute_instance_v2" "k8s_node_no_floating_ip_custom_volume_size" {
name = "${var.cluster_name}-k8s-node-nf-${count.index+1}"
count = "${var.node_root_volume_size_in_gb > 0 ? var.number_of_k8s_nodes_no_floating_ip : 0}"
availability_zone = "${element(var.az_list, count.index)}"
image_name = "${var.image}"
flavor_id = "${var.flavor_k8s_node}"
key_pair = "${openstack_compute_keypair_v2.k8s.name}"
block_device {
uuid = "${data.openstack_images_image_v2.vm_image.id}"
source_type = "image"
volume_size = "${var.node_root_volume_size_in_gb}"
boot_index = 0
destination_type = "volume"
delete_on_termination = true
}
Hope this helps

Related

Combining/merging variables in terraform

I am working within terraform and I am trying to combine variables.
I have been able to do this previously in the format of
name = "${var.name}-${var.environment}"
or something like
domain = "${var.environment}.${var.domain}"
Now I am trying to accomplish something similar but my module that I would like to do this with is utilizing a for_each.
I am trying to specify a host using variables that would represent utility.environment.domain.
Module:
module "aws_alb_listener_rule" {
depends_on = [
module.aws_lb_target_group,
module.aws_alb_listener_https
]
source = "../../terraform/modules/aws_lb_listener_rule_https"
listener_rule = module.aws_alb_listener__https.lb_listener
for_each = var.target_group_listener_rule_values
listener_rule_host_header = "${each.value.host_header}.${var.environment}.${var.domain}"
#listener_rule_host_header = each.value["host_header"]
listener_rule_target_group = module.aws_lb_target_group[each.key].arn
listener_rule_action_type = each.value["action_type"]
Where the first host header is what I was hoping to include but the commented out host header is how I currently have it.
My variables:
variable "target_group_listener_rule_values" {
description = "Specify the target group and listener rule settings and it will create on a 1:1 ratio"
type = map(object({
/*--- Listener Rule ---*/
host_header = list(string)
#host_header = string
target_group = string
action_type = string
my tfvars
listener_rule_values = {
Utility1 = {
/*--- Listener Rule ---*/
"host_header" = ["utility"],
#"host_header" = ["utility.environment.domain"],
"action_type" = "forward",
},
Utility2 = {
/*--- Listener Rule ---*/
"host_header" = ["utility"],
#"host_header" = ["utility.environment.domain"],
"action_type" = "forward",
},
Where the first host header is what I was hoping to include but the commented out host header is how I currently have it.
Child module:
resource "aws_lb_listener_rule" "static" {
listener_arn = var.listener_rule
action {
type = var.listener_rule_action_type
target_group_arn = var.listener_rule_target_group
}
condition {
host_header {
values = var.listener_rule_host_header
}
}
}
What I would like to do is shift this over to use variables instead as I am trying to remove the environment from being entered anywhere in the config except for the "environment" variable.
listener_rule_host_header = "${each.value["host_header"]}.${var.environment}.${var.domain}"
the error I am seeing is
Error: Invalid template interpolation value
│
│ on main.tf line 181, in module "module":
│ 181: listener_rule_host_header = "${each.value["host_header"]}.${var.environment}.${var.domain}"
│ ├────────────────
│ │ each.value["host_header"] is list of string with 1 element
│
│ Cannot include the given value in a string template: string required.
you are very close and your syntax for interpolation is correct. What is not correct is the fact that your each.value["host_header"] is a list, so Terraform complains about that.
I don't know why you want a list, and You did not give your full code, so let me show you a working example with map, that I imagine you want to accomplish:
variable "environment" {
default = "stage"
}
variable "domain" {
default = "example.com"
}
variable "sites" {
type = map(any)
default = {
"utils" = {
"host_header" = "utility"
}
"main" = {
"host_header" = "www"
}
}
}
resource "local_file" "hosts" {
for_each = var.sites
filename = "${each.value.host_header}.${var.environment}.${var.domain}"
content = each.key
}
You can adjust your code based on this example, I hope.
Now, if you insist on using lists, this would look like this:
variable "sites_with_list" {
type = map(any)
default = {
"utils" = {
"host_headers" = ["utility"]
}
"main" = {
"host_headers" = ["www"]
}
}
}
resource "local_file" "hosts_list" {
for_each = var.sites_with_list
filename = "${each.value.host_headers[0]}.${var.environment}.${var.domain}"
content = each.key
}
I did not know how you want to utilize this, so I used a local_file just as an example that will work when copied to empty project.

Is there possibility to dynamicly pass user-defined variable (key = value) to terraform module?

There is resource:
resource "resource_name" "foo" {
name = "test"
config {
version = 14
resources {
disk_type_id = "network-ssd"
}
postgresql_config = {
enable_parallel_hash = true
}
}
}
I need a module which accepts optional user variables in "postgresql_config". There can be many such variables.
I tried next:
variables.tf
variable "postgresql_config" {
description = "User defined for postgresql_config"
type = list(object({
# key1 = value1
# ...
# key50 = value50
}))
}
variable "config" {
description = "for dynamic block 'config' "
type = list(object({
version = number
}))
default = [{
version = 14
}]
}
variable "resources" {
description = "for dynamic block 'resources' "
type = list(object({
disk_type_id = string
}))
default = [{
disk_type_id = "network-hdd"
}]
}
module/postgresql/main.tf
resource "resource_name" "foo" {
name = "test"
dynamic "config" {
for_each = var.config
content {
version = config.value["version"]
dynamic "resources" {
for_each = var.resources
content {
disk_type_id = resources.value["disk_type_id"]
}
}
# problem is here
postgresql_config = {
for_each = var.postgresql_config
each.key = each.value
}
}
}
example/main.tf
module "postgresql" {
source = "../module/postgresql"
postgresql_config = [{
auto_explain_log_buffers = true
log_error_verbosity = "LOG_ERROR_VERBOSITY_UNSPECIFIED"
max_connections = 395
vacuum_cleanup_index_scale_factor = 0.2
}]
That is, I understand that I need to use "dynamic", but it can only be applied to the block "config" and the nested block "resource_name".
How can I pass values for "postgresql_config" from main.tf to module? Of course, my example with for_each = var.postgresql_config doesn't work, but I hope this way to give an idea of what I need.
Or does terraform have no such option to use custom variables dynamically at all, and all of them must be specified explicitly?
Any help would be appreciated, thank you
from what I understand , you are trying to create a map dynamically for your resource postgres_config.
I would recommend using a for expression to solve that problem.
However, I think your problem lies in how you have defined variables for your module . You might run into a problem if your postgress_config list has multiple configs in it because that config can only take a map by the looks of it.
have a look at the following documentation:
this one is for how to define your variables
https://www.terraform.io/language/expressions/dynamic-blocks#multi-level-nested-block-structures
for expressions
https://www.terraform.io/language/expressions/for
my solution for your config problem ,would be something like this assuming that the postgres_config list has one element all the time:
# problem is here
postgresql_config = var.postgresql_config[0]

Iterate over list of maps

I'm trying to iterate over a simple list of maps. Here's a segment of what my module code looks like:
resource "helm_release" "nginx-external" {
count = var.install_ingress_nginx_chart ? 1 : 0
name = "nginx-external"
repository = "https://kubernetes.github.io/ingress-nginx"
chart = "ingress-nginx"
version = var.nginx_external_version
namespace = "default"
lint = true
values = [
"${file("chart_values/nginx-external.yaml")}"
]
dynamic "set" {
for_each = { for o in var.nginx_external_overrides : o.name => o }
content {
name = each.value.name
value = each.value.value
}
}
}
variable "nginx_external_overrides" {
description = "A map of maps to override customizations from the default chart/values file."
type = any
}
And here's a snippet of how I'm trying to call it from terragrunt:
nginx_external_overrides = [
{ name = "controller.metrics.enabled", value = "false" }
]
When trying to use this in a dynamic block, I'm getting:
Error: each.value cannot be used in this context
A reference to "each.value" has been used in a context in which it
unavailable, such as when the configuration no longer contains the value in
its "for_each" expression. Remove this reference to each.value in your
configuration to work around this error.
Ideally, I would be able to pass any number of maps in nginx_external_overrides to override the settings in the yaml being passed, but am struggling to do so. Thanks for the help.
If you are using for_each in dynamic blocks, you can't use each. Instead, in your case, it should be set:
dynamic "set" {
for_each = { for o in var.nginx_external_overrides : o.name => o }
content {
name = set.value.name
value = set.value.value
}
}

Getting Outputs of Resources from Reused Modules

I have the following code that will call a module and make target groups for me based off of the information I pass it in the locals variables. This works just fine, my issue being trying to get the arn of each target group it creates in an output.
locals {
targetgroups_beta = {
targetgroup1 = {
name = "example",
path = "/",
environment = "Beta"
}
targetgroup2 = {
name = "example2",
path = "/",
environment = "Beta"
}
}
}
module "target-groups"{
for_each = local.targetgroups_beta
source = ".//modules/targetgroups"
name-beta = each.value.name
path-beta = each.value.path
environment-beta = each.value.environment
vpc-id = "${module.vpc.vpc_id}"
}
The resource name in the module it is calling is target-group, so based off of what I read I should be able to refer to it by something like this:
output{
value = "${aws_lb_target_group.target-group[0].arn}"
}
When I try this I receive the following when running a terraform plan:
"Because aws_lb_target_group.target-group does not have "count" or "for_each" set, references to it must not include an index key. Remove the bracketed index to refer to the single instance of this resource."
My understanding of this is the module that the for_each is calling isn't running a for_each, so I cannot reference the resources in this way. If I do ""${aws_lb_target_group.target-group.arn}" that reference works technically, but includes the arn for every target group and I plan on adding a lot more. Is there a way to take each of these arns out of this list as its own output?
Code in the module that it is calling for reference:
resource "aws_lb_target_group" "target-group" {
name = "example-${var.name-beta}"
port = 80
protocol = "HTTP"
vpc_id = var.vpc-id
deregistration_delay = 5
tags = {
Environment = "${var.environment-beta}"
}
health_check{
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 10
interval = 15
path = var.path-beta
}
}
If I correctly understand, you are using for_each in your target-groups module. If so to get the outputs, you would have to use something like the following in your main.tf file:
module.target-groups[*].arn
The for_each will create multiple modules, not multiple resources in a single module.
Here is good info on using for_each and count with modules in terraform 0.13.
Update for one module
If you want to use only one module, you can do the following:
module "target-groups"{
target_groups_to_create = local.targetgroups_beta
source = ".//modules/targetgroups"
name-beta = each.value.name
path-beta = each.value.path
environment-beta = each.value.environment
vpc-id = "${module.vpc.vpc_id}"
}
Then in the module:
variable "target_groups_to_create" {}
resource "aws_lb_target_group" "target-group" {
for_each = var.target_groups_to_create
name = "example-${each.value.name}"
port = 80
protocol = "HTTP"
vpc_id = var.vpc-id
deregistration_delay = 5
tags = {
Environment = "${each.value.environment}"
}
health_check{
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 10
interval = 15
path = each.value.path
}
}

Terraform modules azure event subscription optional fields

I am trying to work with terraform modules to create event subscription pointing to storage queue as an endpoint to it.
Below is the module
resource "azurerm_eventgrid_event_subscription" "events" {
name = var.name
scope = var.scope
subject_filter = var.subject_filter
storage_queue_endpoint = var.storage_queue_endpoint
}
and terraform is
module "storage_account__event_subscription" {
source = "../modules/event"
name = "testevent"
scope = test
subject_filter = {
subject_begins_with = "/blobServices/default/containers/test/blobs/in"
}
storage_queue_endpoint = {
storage_account_id = test
queue_name = test
}
}
Error message:
: subject_filter {
Blocks of type "subject_filter" are not expected here.
Error: Unsupported block type
on azure.tf line 90, in module "storage_account__event_subscription":
: storage_queue_endpoint {
Blocks of type "storage_queue_endpoint" are not expected here.
How do i parse the optional fields properly in terraform modules ?
In you module:
resource "azurerm_eventgrid_event_subscription" "events" {
name = var.name
scope = var.scope
subject_filter = {
subject_begins_with = var.subject_begins_with
}
storage_queue_endpoint = var.storage_queue_endpoint
}
Formatting is off here so make sure to run terraform fmt to account for my poor formatting. Also add the variable to the variables.tf file.
Your Terraform file:
module "storage_account__event_subscription" {
source = "../modules/event"
name = "testevent"
scope = test
subject_begins_with = "/blobServices/default/containers/test/blobs/in"
storage_queue_endpoint = {
storage_account_id = test
queue_name = test
}
}
You create the full structure in the module and then you assign the variables in the terraform file.
Anything that will have the same, or generally the same, value can have a default value set in the variables.tf as well so that you get smaller chunks in the TF file.

Resources