Common variables for multiple modules - terraform

Is this the right way to share variables across modules or am I doing it wrong?
In a base module I add a variable:
variable "aws_region" {
description = "Region to use"
default = "eu-west-1"
}
Then I expose it using output:
output "AwsRegion" {
value = var.aws_region
}
Then in the module I want to use it from, I import the base module and refer to it:
module "base" {
source = "../base"
}
provider "aws" {
version = "~> 2.37"
region = module.base.AwsRegion
}
Is there a way to define the variable and export it as output in one go? Does Terraform have the concept of access modifiers? It would be great to be able to do public variable or output variable...

I'm not positive it's the best way, but I've been passing them in at the main.tf file. By doing this I can see the new variables being defined as well as the values that are populating the modules' variables.
module "linuxvm" {
source = "./linuxvm"
vmprefix = var.prefix
resourcegroup = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
vmos = var.os
password = var.vmpassword
subnetid = module.network.subnetid
}

Related

Creating a dynamic secret variable block within Terraform for Cloud Run

I'm trying to create the following block dynamically based on a list of strings
env {
name = "SECRET_ENV_VAR"
value_from {
secret_key_ref {
name = google_secret_manager_secret.secret.secret_id
key = "1"
}
}
}
Based off documentation: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_service#example-usage---cloud-run-service-secret-environment-variables
I would like to dynamically add Secrets, and have defined the following dynamic block:
dynamic "env" {
for_each = toset(var.secrets)
content {
name = each.value
value_from {
secret_key_ref {
name = each.value
key = "1"
}
}
}
}
Where secrets is a variable of type list(string)
However, this throws an error: Blocks of type "value_from" are not expected here.
I'm not sure what I'm missing, or where I have incorrectly specified the value_from block.
Could someone point me in the right direction for fixing this up?
UPDATE;
I have also tried to implement this variable as a map, as per the suggestion in the comments on this post. (https://www.terraform.io/docs/language/expressions/dynamic-blocks.html#multi-level-nested-block-structures)
dynamic "env" {
for_each = var.secrets
content {
name = each.key
dynamic "value_from" {
for_each = env.value.name
secret_key_ref {
name = value_from.value.name
key = value_from.value.version
}
}
}
}
However, this also gives the same error. Blocks of type "value_from" are not expected here.
In this example, the secrets variable is defined as a list(any) with this value:
secrets = [
{
name = "SECRET"
version = "1"
}
]
You have to upgrade your gcp provider. Support for secrets in google_cloud_run_service was added in v3.67.0. Current version is v4.1.0, which means that you must be using very old gcp provider.
In the end, I solved this by changing the variable type to a map(any):
secrets = {
"SECRET" = "1"
}
This allowed me to create the "dynamic" env block, without needing to implement the nested dynamic block.

In Terraform, can i have variables in Data Sources?

i will like to know if I can use variables while calling Data sources in terraform:
Instead of having:
data.terraform_remote_state.dev_vpc
I want to have a variable call dev like:
data.terraform_remote_state.${var.stage}_vpc
I tried to use the variable as it is above but got the error:
Error: Invalid attribute name
on locals.tf line 21, in locals:
21: for n in data.terraform_remote_state.${var.stage}_vpc :
An attribute name is required after a dot.
Any help will be appreciated.
Thanks in advance.
You can't template an identifier like that, but there are a few other ways you could do it.
Use a variable in the remote state definition:
data "terraform_remote_state" "rs" {
backend = "local"
config = {
path = local.remote_path
}
}
This is a little simpler, and lets you template in your remote config using config files, locals, or variables as you want.
Define multiple remote states, and index:
locals {
remote_paths = { dev = "./dev/terraform.tfstate", prod = "./prod/terraform.tfstate" }
}
data "terraform_remote_state" "rs" {
for_each = local.remote_paths
backend = "local"
config = {
path = each.value
}
}
You can now reference dev and prod at data.terraform_remote_state.rs["dev"] or data.terraform_remote_state.rs["prod"]
You can do something like this:
locals {
stage_map = {
¨variable1 name¨ = data.terraform_remote_state.variable1_value
¨variable2 name¨ = data.terraform_remote_state.variable2_value
}
}
resource "xxxx" "this" {
name = var.scp_name
content = local.stage_map[var.stage]
}

Passing data as variable to Terraform module

I am looking for a way to pass data template_cloudinit_config to another module. I am clear about how to pass variables to module for various data types including object, but I am not sure how to do this with data.
In this setup, I have a vm-basic module that will define all the virtual hardware configuration, and postgres Terraform script that will define service related information including cloud init scripts. The intention is to have vm virtual hardware configuration to be highly reusable as module, to allow me focusing only on service related info i.e. postgres, nginx etc.
This is my vm-basic vars.tf file that will accept parameters that will be used in virtual hardware configuration.
variable "prefix" {}
variable "rg" { type = object({
name = string
location = string
}) }
variable "vm_size" {}
variable "private_ip_address" {}
variable "subnet" { type = object({ id = string }) }
variable "data_disk_size_gb" { type = number }
variable "service_name" { type = string }
variable "admin_username" { type = string }
variable "admin_public_key_path" { type = string }
variable "nsg_allow_tcp_ports" { type = list(string) }
locals {
nsg_allow_tcp_ports = {for p in var.nsg_allow_tcp_ports: index(var.nsg_allow_tcp_ports, p) => p}
}
#### DOES NOT WORK ######
#### Expected an equals sign ("=") to mark the beginning of the attribute value. ######
variable "custom_data" { type = object({ data }) }
How custom data will be used in vm-basic module
resource "azurerm_linux_virtual_machine" "vm" {
name = "${var.prefix}-${var.service_name}-vm"
location = var.rg.location
resource_group_name = var.rg.name
...
...
custom_data = var.custom_data.rendered
...
...
}
How the other script will pass parameter to vm-basic module
module "vm-basic" {
source = "../../base/vm"
service_name = var.service_name
prefix = var.prefix
rg = var.rg
vm_size = var.vm_size
private_ip_address = var.private_ip_address
subnet = var.subnet
data_disk_size_gb = var.data_disk_size_gb
admin_username = var.admin_username
admin_public_key_path = var.admin_public_key_path
nsg_allow_tcp_ports = var.nsg_allow_tcp_ports
}
data "template_cloudinit_config" "config" {
gzip = true
base64_encode = true
part {
filename = "init-cloud-config"
content_type = "text/cloud-config"
content = file("init.yaml")
}
part {
filename = "init-shellscript"
content_type = "text/x-shellscript"
content = templatefile("init.sh",
{ hostname = "${var.prefix}-${var.service_name}" }
)
}
}
How can I pass data object to another Terraform module?
In the variable vars.tf file, it's enough to just do
variable "custom_data" {}
In the vm-basic module, refer to the variable through var, similar to others.
custom_data = var.custom_data.rendered
The meaning of the error you saw is that Terraform is expecting the argument to the object type constraint to be name = type pairs, but you only wrote data and so Terraform is reporting that there's a missing =.
To make this work, you'll need to write a valid type constraint. It's not clear to me from your question exactly what custom_data represents, but I do see your later example includes var.custom_data.rendered and so from that I can tell that the type constraint should at least include a rendered attribute in order to make that valid, and the custom_data argument to azurerm_linux_virtual_machine expects a string so I'll match that:
variable "custom_data" {
type = object({
rendered = string
})
}
This means that Terraform will accept any object value which has a rendered attribute that can convert to string, and thus your later reference to var.custom_data.rendered is guaranteed to work and always produce a string value.

Create multiple aws_cloudformation_stack based on parametrized name with Terraform

Is it possible to create multiple CloutFormation stacks with one aws_cloudformation_stack resource definition in terraform, based on parametrized name ?
I have the following resources defined and I would like to have a stack per app_name, app_env build_name combo:
resource "aws_s3_bucket_object" "sam_deploy_object" {
bucket = var.sam_bucket
key = "${var.app_env}/${var.build_name}/sam_template_${timestamp()}.yaml"
source = "../.aws-sam/sam_template_output.yaml"
etag = filemd5("../.aws-sam/sam_template_output.yaml")
}
resource "aws_cloudformation_stack" "subscriptions_sam_stack" {
name = "${var.app_name}---${var.app_env}--${var.build_name}"
capabilities = ["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
template_url = "https://${var.sam_bucket}.s3-${data.aws_region.current.name}.amazonaws.com/${aws_s3_bucket_object.sam_deploy_object.id}"
}
When I run terraform apply when build_name name changes, the old stack gets deleted and a new one created, however I would like to keep the old stack and create a new one
One way would be to define your variable build_name as a list. Then, when you create new build, you just append them to the list, and create stacks with the help of for_each to iterate over the build names.
For example, if you have the following:
variable "app_name" {
default = "test1"
}
variable "app_env" {
default = "test2"
}
variable "build_name" {
default = ["test3"]
}
resource "aws_cloudformation_stack" "subscriptions_sam_stack" {
for_each = toset(var.build_name)
name = "${var.app_name}---${var.app_env}--${each.value}"
capabilities = ["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
template_url = "https://${var.sam_bucket}.s3-${data.aws_region.current.name}.amazonaws.com/${aws_s3_bucket_object.sam_deploy_object.id}"
}
Then if you want second build for the stack, you just extend variable "build_name":
variable "build_name" {
default = ["test3", "new_build"]
}

Terraform dynamic variable

I'm creating subnets as part of a seperate terraform template and exporting the IDs as follows.
output "subnet-aza-dev" {
value = "${aws_subnet.subnet-aza-dev.id}"
}
output "subnet-azb-dev" {
value = "${aws_subnet.subnet-azb-dev.id}"
}
output "subnet-aza-test" {
value = "${aws_subnet.subnet-aza-test.id}"
}
output "subnet-azb-test" {
value = "${aws_subnet.subnet-azb-test.id}"
}
...
I'm then intending to lookup these IDs in another template which is reused to provision multiple environments. Example below shows my second template is calling a module to provision an EC2 instance and is passing through the subnet_id.
variable "environment" {
description = "Environment name"
default = "dev"
}
module "sql-1-ec2" {
source = "../modules/ec2winserver_sql"
...
subnet_id = "${data.terraform_remote_state.env-shared.subnet-aza-dev}"
}
What I'd like to do is pass the environment variable as part of the lookup for the subnet_id e.g.
subnet_id = "${data.terraform_remote_state.env-shared.subnet-aza-${var.environment}"
However I'm aware that variable interpolation isn't supported. I've tried using a map inside of the first terraform template to export them all to a 'subnet' which I could then use to lookup from the second template. This didn't work as I was unable to output variables inside of the map.
This sort of design pattern is something I've used previously with CloudFormation, however I'm much newer to terraform. Am I missing something obvious here?
Worked out a way to do this using data sources
variable "environment" {
description = "Environment name"
default = "dev"
}
module "sql-1-ec2" {
source = "../modules/ec2winserver_sql"
...
subnet_id = "${data.aws_subnet.subnet-aza.id}"
}
data "aws_subnet" "subnet-aza" {
filter {
name = "tag:Name"
values = ["${var.product}-${var.environment}-${var.environmentno}-subnet-aza"]
}
}
data "aws_subnet" "subnet-azb" {
filter {
name = "tag:Name"
values = ["${var.product}-${var.environment}-${var.environmentno}-subnet-azb"]
}
}
Whilst this works and fulfils my original need, I'd like to improve on this by moving the data blocks to within the module, so that there's less repetition. Still working on that one though...

Resources