Unable to generate out for id and arn - amazon-web-services

I am using terraform 1.0.11
I am trying to set an output of my secret manger.
main.tf:
resource "aws_secretsmanager_secret" "aws_secret" {
for_each = { for secret in var.secrets : secret.secret_name => secret}
name = each.value.secret_name
}
output.tf:
output "secret_arns" {
value = tolist(aws_secretsmanager_secret.aws_secret[*].arn)
}
However, it is throwing me this error.
╷
│ Error: Unsupported attribute
│
│ on ../Resources/secrets/outputs.tf line 2, in output "secret_arns":
│ 2: value = tolist(aws_secretsmanager_secret. aws_secret[*].arn)
│
│ This object does not have an attribute named "arn".
Is there anything that I did wrongly here?

Your aws_secretsmanager_secret.aws_secret is a map. So it should be:
output "secret_arns" {
value = values(aws_secretsmanager_secret.aws_secret)[*].arn
}

Related

Iterate On Module Instances Output When Declared With `for_each`

Description
I'm attempting to iterate on the results of a terraform module and build a set of AWS glue crawlers and IAM roles based on how many source S3 buckets are set in the terraform module
I'm specifically trying to use this line
for_each = {for key, value in setproduct(local.bucket-names, module.crawler_source_buckets.id): key=>value}
or this line
for_each = {for key, value in setproduct(local.bucket-names, module.crawler_source_buckets.*.id): key=>value}
or this line
for_each = {for key, value in setproduct(local.bucket-names, module.crawler_source_buckets.[*].id): key=>value}
but these all result in different errors such as
│ Error: Invalid function argument
│
│ on catalog.tf line 11, in resource "aws_glue_crawler" "bucket_crawlers":
│ 11: for_each = {for key, value in setproduct(local.bucket-names, module.crawler_source_buckets.id): key=>value}
│ ├────────────────
│ │ while calling setproduct(sets...)
│ │ module.crawler_source_buckets.id is a object
│
│ Invalid value for "sets" parameter: a set or a list is required.
or
│ Error: Invalid template interpolation value
│
│ on catalog.tf line 14, in resource "aws_glue_crawler" "bucket_crawlers":
│ 14: path = "s3://${each.value[1]}"
│ ├────────────────
│ │ each.value[1] is object with 2 attributes
│
│ Cannot include the given value in a string template: string required.
which is making me question if the thing I want to do is even possible. Maybe the answer in this case is that I use the base variable declarations since AWS S3 Ids and ARNs follow a known standard pattern but I would like to know how to iterate on module outputs that are declared with a for_each pattern since that seems very useful.
Code
s3.tf
locals {
bucket-names = [
"${var.env_name}-failed-stripe-payment-reports",
"${var.env_name}-fraud-log-return-responses"]
}
module "crawler_source_buckets" {
source = "../../modules/s3-bucket-v2"
for_each = toset(local.bucket-names)
bucket_name = each.value
# There's more code but I'm choosing to leave it out for clairty
]
}
s3-bucket-v2 output.tf
output "arn" {
description = "The ARN of the main bucket."
value = aws_s3_bucket.main.arn
}
output "id" {
description = "The ID (name) of the main bucket."
value = aws_s3_bucket.main.id
}
# There's an extra 60 lines I'm choosing to leave out for clarity.
catalog.tf
resource "aws_glue_catalog_database" "catalog_databases" {
name = each.value
for_each = toset(local.bucket-names)
}
resource "aws_glue_crawler" "bucket_crawlers" {
database_name = each.value[0]
name = "${each.value[0]}-crawler"
role = aws_iam_role.glue_crawler_role.arn
for_each = {for key, value in setproduct(local.bucket-names, module.crawler_source_buckets.id): key=>value}
s3_target {
path = "s3://${each.value[1]}"
}
schedule = "cron(*/15 * * * ? *)"
}
Since you are using for_each in the module you have to access it using key. For example, module.crawler_source_buckets["key"].id. To get all ids you have to use the following:
values(module.crawler_source_buckets)[*].id
So it should be:
for_each = {for key, value in setproduct(local.bucket-names, values(module.crawler_source_buckets)[*].id): key=>value}

How to grab two arbitrary subnet IDs from Terraform

Looking to deploy a Fargate application where the invoking function runs the ECS container in two arbitrarily selected subnets from the default VPC.
So far my template looks like this:
data "aws_subnets" "subnets" {
filter {
name = "vpc-id"
values = [var.vpc_id]
}
}
data "aws_subnet" "subnet" {
for_each = toset(data.aws_subnets.subnets.ids)
id = each.value
}
resource "aws_lambda_function" "ecs_invoker" {
function_name = "ecs_invoker"
...
environment {
variables = {
SUBNET_PRIMARY = data.aws_subnet.subnet[0]
SUBNET_SECONDARY = data.aws_subnet.subnet[1]
}
}
}
However, this produces the following error:
│ Error: Invalid index
│
│ on lambda.tf line 16, in resource "aws_lambda_function" "ecs_invoker":
│ 16: SUBNET_PRIMARY = data.aws_subnet.subnet[0]
│ ├────────────────
│ │ data.aws_subnet.subnet is object with 6 attributes
│
│ The given key does not identify an element in this collection value. An object only supports looking up attributes by name, not by numeric index.
╵
So how exactly should I grab two arbitrary subnet IDs from the default VPC?
Since you used for_each, data.aws_subnet.subnet will be a map, not a list. So to get two first subnet ids, you can do:
SUBNET_PRIMARY = values(data.aws_subnet.subnet)[0].id
SUBNET_SECONDARY = values(data.aws_subnet.subnet)[1].id
To get two random ids, you can do:
resource "random_shuffle" "subnets" {
input = values(data.aws_subnet.subnet)[*].id
result_count = 2
}
and then
SUBNET_PRIMARY = random_shuffle.subnets.result[0]
SUBNET_SECONDARY = random_shuffle.subnets.result[1]

pass list(strings) as a terraform variable

While defining my module, I do this:
module "activate_alarms" {
source = "../_modules/aws/.."
config = module.config
alarm_arns = ["arn:aws:cloudwatch:eu-central-123:test-alarm"]
}
variable "alarm_arns" {
type = list(string)
}
resource "aws_lambda_function" "processing_lambda" {
filename = data.archive_file.lambda_zip.output_path
function_name = local.processing_lambda_name
handler = "enable_disable_alarms.lambda_handler"
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
role = aws_iam_role.lambda_role.arn
runtime = "python3.9"
tags = var.config.tags
environment {
variables = {
alarm_arns = local.alarm_arns
}
}
}
However, I get an error:
Error: Incorrect attribute value type
│
│ on ../_modules/aws/enable_disable_alarms/processing_lambda.tf line 20, in resource "aws_lambda_function" "processing_lambda":
│ 20: variables = {
│ 23: alarm_arns = local.alarm_arns
│ 30: }
│ ├────────────────
│ │ local.alarm_arns is a list of string, known only after apply
│
│ Inappropriate value for attribute "variables": element "alarm_arns": string required.
What's the correct way to pass a list of strings?
Edit:
locals {
config = var.config
alarm_arns = "${var.alarm_arns}"
}
You can't pass list(string) directly as environment into lambda. You have to make it into string first, e.g.:
environment {
variables = {
alarm_arns = jsonencode(var.alarm_arns)
}
}
Then in your lambda you would have to convert it back to list of strings according to your programming language.

Kubernetes Manifest Terraform Virtual Node AWS AppMesh

I am trying to update my Virtual Node for AWS App Mesh via its Terraform configuration (that has worked and been deployed prior). I am adding a serviceDiscovery ReponseType variable to it so that I can change that value. Currently my code looks like this:
# Virtual Node Creation --------------------------------
resource "kubernetes_manifest" "service_virtual_node" {
manifest = {
"apiVersion" = "appmesh.k8s.aws/v1beta2"
"kind" = "VirtualNode"
"metadata" = {
"name" = "${var.team_name}-${var.service_name}-virtual-node"
"namespace" = "${var.kubernetes_namespace}"
}
"spec" = {
"podSelector" = {
"matchLabels" = {
"app" = "${var.service_name}"
}
}
"listeners" = [{
"portMapping" = {
"port" = "${var.appmesh_port}"
"protocol" = "${var.service_protocol}"
}
}]
"serviceDiscovery" = {
"dns" = {
"hostname" = var.load_balancer_type == "internal" ? "${var.service_name}.${var.dns_zone_name}" : "${var.service_name}-external.${var.dns_zone_name}"
"responseType" = var.service_discovery_response_type
}
}
logging = {
accessLog = {
file = {
path = "/dev/stdout"
}
}
}
}
}
}
It seems to be complaining about my new line:
"responseType" = var.service_discovery_response_type
The weird part about this, is that I deployed the same code (terraform module) to three other services without complaint. The error that I am now recieving from this is:
╷
│ Error: Failed to morph manifest to OAPI type
│
│ with module.services.module.accounts-api.module.appmesh.kubernetes_manifest.service_virtual_node,
│ on .terraform/modules/services.accounts-api.appmesh/kubernetes_manifest.tf line 2, in resource "kubernetes_manifest" "service_virtual_node":
│ 2: resource "kubernetes_manifest" "service_virtual_node" {
│
│ AttributeName("spec"): [AttributeName("spec")] failed to morph object element into object element: AttributeName("spec").AttributeName("serviceDiscovery"):
│ [AttributeName("spec").AttributeName("serviceDiscovery")] failed to morph object element into object element: AttributeName("spec").AttributeName("serviceDiscovery").AttributeName("dns"):
│ [AttributeName("spec").AttributeName("serviceDiscovery").AttributeName("dns")] failed to morph object element into object element:
│ AttributeName("spec").AttributeName("serviceDiscovery").AttributeName("dns").AttributeName("responseType"):
│ [AttributeName("spec").AttributeName("serviceDiscovery").AttributeName("dns").AttributeName("responseType")] failed to morph object element into object element:
│ AttributeName("spec").AttributeName("serviceDiscovery").AttributeName("dns").AttributeName("responseType"): type is nil
I am not sure why the same code is now failing. I have tried adding "${}" around the variable, and also adding brackets as well. Both failed.

Extracting list of subnets result in error - is tuple with 1 element

I have a module that creates a VPC with public and private subnets
module "vpc" {
count = var.vpc_enabled ? 1 : 0
source = "./vpc"
}
and as an output of that module I'm extracting the private subnets
output "private_subnets" {
value = aws_subnet.private.*.id
}
Then I want to use that subnets list as an input of another module:
module "eks" {
source = "./eks"
name = var.name
private_subnets = var.vpc_enabled ? module.vpc.private_subnets : var.private_subnets_id
}
basically what I'm trying to achieve is that the user can choose if he want to create a new VPC or use as an input a list of subnets of their existing VPC.
The problem that I've right now is that I'm getting the following error in terraform plan:
on main.tf line 32, in module "eks":
32: private_subnets = var.vpc_enabled ? module.vpc.private_subnets : var.private_subnets_id
|----------------
| module.vpc is tuple with 1 element
This value does not have any attributes.
Does anyone knows how to fix this?
You are defining your vpc module with count. Thus you need to refer to individual instances of the module, even if you have only 1.
private_subnets = var.vpc_enabled ? module.vpc[0].private_subnets : var.private_subnets_id
Just to add Marcin's answer
I had a similar issue when working with dynamic blocks and locals in Terraform.
I had a locals block like this:
locals {
subnet_suffix = "dev-subnet"
delegation_settings = [{
subnet_delegation_name = "app-service-delegation"
subnet_service_delegation_name = "Microsoft.Web/serverFarms"
}]
}
And I was referencing the attributes this way:
module "subnet_public_1" {
source = "../../../modules/azure/subnet"
subnet_name = "${var.subnet_name}-public-1-${local.subnet_suffix}"
resource_group_name = data.azurerm_resource_group.main.name
virtual_network_name = data.azurerm_virtual_network.main.name
subnet_address_prefixes = var.subnet_address_prefixes.public_1
enforce_private_link_endpoint_network_policies = var.enforce_private_link_endpoint_network_policies.public_1
delegation_settings = [
{
subnet_delegation_name = local.delegation_settings.subnet_delegation_name
subnet_service_delegation_name = local.delegation_settings.subnet_service_delegation_name
}
]
tag_environment = var.tag_environment
}
And when I run terraform plan I get the error below:
│ Error: Unsupported attribute
│
│ on main.tf line 68, in module "subnet_public_1":
│ 68: subnet_delegation_name = local.delegation_settings.subnet_delegation_name
│ ├────────────────
│ │ local.delegation_settings is tuple with 1 element
│
│ This value does not have any attributes.
╵
╷
│ Error: Unsupported attribute
│
│ on main.tf line 69, in module "subnet_public_1":
│ 69: subnet_service_delegation_name = local.delegation_settings.subnet_service_delegation_name
│ ├────────────────
│ │ local.delegation_settings is tuple with 1 element
│
│ This value does not have any attributes.
Here's how I solved it:
All I had to do was to add the index to the attributes, in this case it was 0:
module "subnet_public_1" {
source = "../../../modules/azure/subnet"
subnet_name = "${var.subnet_name}-public-1-${local.subnet_suffix}"
resource_group_name = data.azurerm_resource_group.main.name
virtual_network_name = data.azurerm_virtual_network.main.name
subnet_address_prefixes = var.subnet_address_prefixes.public_1
enforce_private_link_endpoint_network_policies = var.enforce_private_link_endpoint_network_policies.public_1
delegation_settings = [
{
subnet_delegation_name = local.delegation_settings[0].subnet_delegation_name
subnet_service_delegation_name = local.delegation_settings[0].subnet_service_delegation_name
}
]
tag_environment = var.tag_environment
}
That's all