We have a custom VPC registry built within our organization. The objective is to enable user of registry to be able to create VPC, create multiple public and private subnets for multiple availability zones within VPC.
I have a for_each variable set at
variable az_sub {list(object(
az = string,
public_cidr_block = list,
private_cidr_block =list
))}
Variable has a value of
[{az='us-east-1a",
public_cidr_block =[list of cidr],
private_cidr_block= [list of cidr]
},
az='us-east-1b",
public_cidr_block =[list of cidr],
private_cidr_block= [list of cidr} ]
When I set for_each on this within registry,
module "az"{
source="./modules/az"
vpc_id = module.vpc.vpc_id
for_each = toset(keys({for i,v in var.az_sub: i => v}))
availability_zone = var.az_sub[each.value]["az"]
public_cidr_block = var.az_sub[each.value]["public_cidr_block"]
private_cidr_block =var.az_sub[each.value]["private_cidr_block"]
}
I get unsupported attribute error on terraform/modules/modReg/output.tf in output public_subnet_ids
module.az is object with 2 attributes
output.tf has the outputs defined.
The az variables.tf has all three variables defined.
Also Note, if I replace for_each with below, do not get errors, but objects are successfully created on aws
availability_zone="us-east-1a"
public_cidr_block = ["10.97.224.0/22"]
private_cidr_block =["10.97.228.0/22"]
So doubt its issue with az module
Note, if I use only 1 az zone on the variable value for list(object) I the error changes to
module.az is object with 1 attribute "us-east-1"
output "private_subnet_ids" {
description = "List of private subnet IDs"
value = values(module.az).*.private_subnet_ids
}
For anyone having same issue, this resolved the problem. I had used list(object and not map(object. If map(object was used, then output.tf would need to have map values.
If you use for_each to send values to the module, the outputs would need to be defined as shown above.
Related
I need a help with a terraform module that I've created. It works perfectly, but I need to add some automation.
I created a module which creates multiple private endpoints, but I always need to put the variable values in manually.
This is the module:
resource "azurerm_private_endpoint" "endpoint" {
for_each = try({ for endpoint in var.endpoints : endpoint.name => endpoint }, toset([]))
name = each.key
location = var.location
resource_group_name = var.resource_group_name
subnet_id = each.value.subnet_id
dynamic "private_service_connection" {
for_each = each.value.private_service_connection
content {
name = each.key
private_connection_resource_id = private_service_connection.value.private_connection_resource_id
is_manual_connection = false
subresource_names = var.subresource_name ### see values on : https://learn.microsoft.com/fr-fr/azure/private-link/private-endpoint-overview#private-link-resource
}
}
lifecycle {
ignore_changes = [
private_dns_zone_group
]
}
tags = var.tags
}
I need to have:
1 - for the private endpoint name : I need it to be automatically provided: "pendp-(the subresource_name value in lower cases- my resource_name =>(mysql server for example))"
2 - for the private connection name: I need the values to be automatically: "connection-(the subresource_name value in lower cases- my ressource_name =>(mysql server for exemple))"
3 - some automation to detect automatically the subresource_name ( if I create a private endpoint for a blob or for a mariadb or for a mysqlserver, the module should detected it.
terraform version:
terraform {
required_version = "~> 1"
required_providers {
azurerm = "~> 3.0"
}
}
The easiest way to combine values automatically would be to use the Terraform string join() function to join multiple strings together. For lower case strings, you can use the lower() function.
Some examples:
name = join("-", ["pandp", lower(var.subresource_name)])
...
name = join("-", ["connection", lower(var.subresource_name), lower(each.key)])
For your third rule, you want to use a conditional expression to determine if it's a blob, or mariadb, or mysqlserver.
In this example, we set an example_name local with a value some-blob-value if var.subresource_name contains a string that starts with "blob", and set it to something-else if the condition is false:
locals {
example_name = startswith(lower(var.subresource_name), "blob") ? "some-blob-value" : "something-else"
}
There are many options available for doing a conditional on if a value is passed to what you expect and then determine a result based on that value. What exactly you want isn't clear in the question, but hopefully this will get you pointed in the right direction.
Terraform even has several helper functions that might help you if you only need part of a string, such as startswith(), endswith(), or contains() depending on your needs.
I have the following in my module to create a role:
resource "aws_iam_role" "default" {
name = var.name
assume_role_policy = var.assume_role_policy
permissions_boundary = var.account_id != "" ? var.permissions_boundary : "arn:aws:iam::${data.aws_caller_identity.default.account_id}:policy/BoundedPermissionsPolicy"
}
Problem - I want to be able to set the permissions boundary argument to use the account ID if present and if not specified then make use of var.permissions_boundary (arn:aws:iam::${data.aws_caller_identity.default.account_id}:policy/BoundedPermissionsPolicy).
The code above does not work when I try to use the account ID.
I am facing a problem and workflow related to terraform to automate the creation of storage account, key vaults and access policies.
What I am trying to achieve is as follow:
I have a storage-account that runs with a for_each loop:
//==================================================
// Automation storage accounts
//==================================================
resource "azurerm_storage_account" "storage-foreach" {
for_each = var.storage-foreach
access_tier = "Hot"
account_kind = "StorageV2"
account_replication_type = "LRS"
account_tier = "Standard"
location = var.location
name = each.value
resource_group_name = azurerm_resource_group.tenant-testing-hamza.name
depends_on = [azurerm_key_vault_key.client-key]
identity {
type = "SystemAssigned"
}
lifecycle {
prevent_destroy = false
}
}
this storage account resource, loops through this variable to create the storage accounts
variable "storage-foreach" {
type = map(string)
default = { "storage1" = "storage1", "storage2" = "storage2", "storage3" = "storage3", "storage4" = "storage4"}
}
so far everything works smoothly. Than I wanted to add those storage accounts object id to my key vault access policy, as follow:
resource "azurerm_key_vault_access_policy" "storage" {
for_each = var.storage-foreach
key_vault_id = azurerm_key_vault.tenantsnbshared.id
tenant_id = "<tenant-id"
object_id = azurerm_storage_account.storage-foreach[each.key].identity.0.principal_id
key_permissions = ["get", "Create", "List", "Restore", "Recover", "Unwrapkey", "Wrapkey", "Purge", "Encrypt", "Decrypt", "Sign", "Verify"]
secret_permissions = ["get", "set", "list", "delete", "recover"]
}
so far everything works just fine while creating the resource, I have all the access policies in place. But, If I try to remove, for example the storage1 from my variable, the storage account get deleted and the access policies related to that specific storage, which is good.
And here the main issue I am facing. If I try to add again the same storage in the variable and run a terraform apply , what happen is that the 3 policies still existing they get removed and the access policy for the storage account get created. If I do one more time terraform apply the logic get inverted, it will delete the first storage account access policy and add the other 3.
I can't find a solution to just update my access policies accordingly to the element I have set in my variable.
In terraform , Trying to S3 bucket as trigger to my lambda and giving the permissions. For this use case , creating S3 resource and trying to refer that lambda function in triggering logic. But When I refer code is failing with below error.
# Creating Lambda resource
resource "aws_lambda_function" "test_lambda" {
filename = "output/welcome.zip"
function_name = var.function_name
role = var.role_name
handler = var.handler_name
runtime = var.run_time
}
# Creating s3 resource for invoking to lambda function
resource "aws_s3_bucket" "bucket" {
bucket = "source-bucktet-testing"
acl = "private"
tags = {
Name = "source-bucktet-testing"
Environment = "Dev"
}
}
# Adding S3 bucket as trigger to my lambda and giving the permissions
resource "aws_s3_bucket_notification" "aws-lambda-trigger" {
bucket = "aws_s3_bucket.bucket.id"
lambda_function {
lambda_function_arn = "aws_lambda_function.test_lambda.arn"
events = ["s3:ObjectCreated:*"]
filter_prefix = "file-prefix"
filter_suffix = "file-extension"
}
}
resource "aws_lambda_permission" "test" {
statement_id = "AllowS3Invoke"
action = "lambda:InvokeFunction"
function_name = "aws_lambda_function.test_lambda.function_name"
principal = "s3.amazonaws.com"
source_arn = "arn:aws:s3:::aws_s3_bucket.bucket.id"
}
Error Message :
The value passed to the aws_lambda_function resource for function_name is invalid. An AWS Lambda function name can only contain letters, numbers, hyphens, or underscores with no spaces. You need to change the value of var.function_name to align with these restrictions.
Your var.function_name must be invalid.
The allowed function name format is explained here along with ARN:
The length constraint applies only to the full ARN. If you specify only the function name, it is limited to 64 characters in length.
Type: String
Length Constraints: Minimum length of 1. Maximum length of 140.
Pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}(-gov)?-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\$LATEST|[a-zA-Z0-9-_]+))?
I'm writing terraform templates to create two S3 buckets, however, my requirement is to concatenate their names in vars.tf and then pass it to main tf file. Below is the vars.tf and main s3.tf file.
vars.tf:
variable TENANT_NAME {
default = "Mansing"
}
variable BUCKET_NAME {
type = "list"
default = ["bh.${var.TENANT_NAME}.o365.attachments", "bh.${var.TENANT_NAME}.o365.eml"]
}
s3.tf:
resource "aws_s3_bucket" "b" {
bucket = "${element(var.BUCKET_NAME, 2)}"
acl = "private"
}
When do terraform plan I get an error indicating that var may not work here.
Error: Variables not allowed
on vars.tf line 10, in variable "BUCKET_NAME":
10: default = ["bh.${var.TENANT_NAME}.o365.attachments", "bh.${var.TENANT_NAME}.o365.eml"]
Variables may not be used here.
Error: Variables not allowed
on vars.tf line 10, in variable "BUCKET_NAME":
10: default = ["bh.${var.TENANT_NAME}.o365.attachments", "bh.${var.TENANT_NAME}.o365.eml"]
Variables may not be used here.
I tried replacing var in vars file with locale but did not work.
You can use Terraform locals block to concatenate variable values in the s3.tf file:
locals {
BUCKET_NAME = [
"bh.${var.TENANT_NAME}.o365.attachments",
"bh.${var.TENANT_NAME}.o365.eml"
]
}
resource "aws_s3_bucket" "b" {
bucket = "${element(local.BUCKET_NAME, 2)}"
acl = "private"
}