Missing resource instance key when using for_each in terraform - terraform

I am creating multiple s3 buckets using for_each in terraform. Here he the code I am using
resource "aws_s3_bucket" "s3_private" {
for_each = var.git_repo_branch_env
bucket = each.value.override_domain_name == "" ? each.value.sitename_prefix == "" ? each.value.domain_name : join(".", [each.value.sitename_prefix, each.value.domain_name]) : each.value.sitename_prefix == "" ? each.value.override_domain_name : join(".", [each.value.sitename_prefix, each.value.override_domain_name])
force_destroy = true
}
I would like to set the ACL property for each of the buckets created, here is the code that I tried using
resource "aws_s3_bucket_acl" "s3_private_acl" {
bucket = aws_s3_bucket.s3_private.bucket
acl = "private"
}
I get the following error message with that
│ Error: Missing resource instance key │ │ on
../../modules/cloudfront-edge-auth-acp/main.tf line 149, in resource
"aws_s3_bucket_acl" "s3_private_acl": │ 149: bucket =
aws_s3_bucket.s3_private.bucket │ │ Because aws_s3_bucket.s3_private
has "for_each" set, its attributes must be │ accessed on specific
instances. │ │ For example, to correlate with indices of a referring
resource, use: │ aws_s3_bucket.s3_private[each.key]
I get that the error message is because I have a for_each on my bucket resource and I need to add the ACL property for each of the buckets. But I am unsure of how to add the ACL property to each of the buckets.
Question: How do I assign the ACL property to each of the buckets created using for_each?

If you created multiple buckets using for_each, same you need to do with ACLs:
resource "aws_s3_bucket_acl" "s3_private_acl" {
for_each = aws_s3_bucket.s3_private
bucket = each.value.bucket
acl = "private"
}
It's well explained in Terraform documentation: chaining for_each between resources.

Related

Iterating through a nested list in dynamic block in terraform

I want to be able to create vault policies from a yaml configuration as follows
policies:
policy-test-1:
- capabilities:
- read
- create
path: /foo/lala
- capabilities:
- create
- patch
path: /voo/lala
policy-test-2:
- capabilities:
- update
- delete
path: /foo/lala
i.e. I want to provision so that each policy document may be able to have multiple statements.
var.policy_statements is the output of yamldecode of the above file.
The following approach using dynamic blocks however fails
data "vault_policy_document" "this" {
dynamic "rule" {
for_each = var.policy_statements
content {
path = rule.value.path
capabilities = rule.value.capabilities
}
}
}
resource "vault_policy" "this" {
for_each = var.policy_statements
name = each.key
policy = data.vault_policy_document.this[each.key].hcl
}
│ Error: Unsupported attribute
│
│ on ../path/to/policies/main.tf line 8, in data "vault_policy_document" "this":
│ 8: capabilities = rule.value.capabilities
│ ├────────────────
│ │ rule.value is list of object with 2 elements
│
│ Can't access attributes on a list of objects. Did you mean to access
│ attribute "capabilities" for a specific element of the list, or across all
│ elements of the list?
╵
╷
│ Error: Unsupported attribute
│
│ on ../path/to/policies/main.tf line 8, in data "vault_policy_document" "this":
│ 8: capabilities = rule.value.capabilities
│ ├────────────────
│ │ rule.value is list of object with 1 element
│
│ Can't access attributes on a list of objects. Did you mean to access
│ attribute "capabilities" for a specific element of the list, or across all
│ elements of the list?
What is the correct way to run the loop, given that each object with the policy name (e.g. policy-test-1) is an array of objects having as keys paths and capabilities?
UPDATE: I have tried the following approach
locals {
policies = flatten([
for k, v in var.policy_statements : [
for i, j in v : {
capabilities = j.capabilities
path = j.path
name = k
}
]
])
}
data "vault_policy_document" "this" {
dynamic "rule" {
for_each = local.policies
content {
path = rule.path
capabilities = rule.capabilities
}
}
}
resource "vault_policy" "this" {
for_each = local.policies
name = each.value.name
policy = data.vault_policy_document.this[each.key].hcl
}
The error now is
20: capabilities = rule.capabilities
│
│ This object does not have an attribute named "capabilities".
(same with path)
Why doesn't the local.policies haven't path/capabilities attributes, given that I am explicitly creating them
Why doesn't the local.policies haven't path/capabilities attributes, given that I am explicitly creating them
Because your first element in the yaml file is policies. So you have to start iterating from that (added .policies):
locals {
policies = flatten([
for k, v in var.policy_statements.policies : [
for i, j in v : {
capabilities = j.capabilities
path = j.path
name = k
}
]
])
}

jsondecode fails when using for_each to pass variables to module

I'm trying to use for_each with a terraform module creating datadog synthetic tests. The object names in an s3 bucket are listed and passed as the set for the for_each. The module reads the content of each file using the each.value passed in by the calling module as the key. I hardcoded the s3 object key value in the module during testing and it was working. When I attempt to call the module from main.tf, passing in the key name dynamically from the set it fails with the below error.
│ Error: Error in function call
│
│ on modules\Synthetics\trial2.tf line 7, in locals:
│ 7: servicedef = jsondecode(data.aws_s3_object.tdjson.body)
│ ├────────────────
│ │ data.aws_s3_object.tdjson.body is ""
│
│ Call to function "jsondecode" failed: EOF.
main.tf
data "aws_s3_objects" "serviceList" {
bucket = "bucketname"
}
module "API_test" {
for_each = toset(data.aws_s3_objects.serviceList.keys)
source = "./modules/Synthetics"
S3key = each.value
}
module
data "aws_s3_object" "tdjson" {
bucket = "bucketname"
key = var.S3key
}
locals {
servicedef = jsondecode(data.aws_s3_object.tdjson.body)
Keys = [for k,v in local.servicedef.Endpoints: k]
}
Any clues as to what's wrong here?
Thanks
Check out the note on the aws_s3_object data source:
The content of an object (body field) is available only for objects which have a human-readable Content-Type (text/* and application/json). This is to prevent printing unsafe characters and potentially downloading large amount of data which would be thrown away in favour of metadata.
Since it's successfully getting the data source (not throwing an error), but the body is empty, this is very likely to be your issue. Make sure that your S3 object has the Content-Type metadata set to application/json. Here's a Stack Overflow question/answer on how to do that via the CLI; you can also do it via the AWS console, API, or Terraform (if you created the object via Terraform).
EDIT: I found the other issue. Check out the syntax for using for_each with toset:
resource "aws_iam_user" "the-accounts" {
for_each = toset( ["Todd", "James", "Alice", "Dottie"] )
name = each.key
}
The important bit is that you should be using each.key instead of each.value.

error when creating rds event subscription

Below tf code is to create AWS RDS event subscriptions. Getting below error while running this piece of tf code. If source_ids are not passed then it seems to be using 'all' as the default value - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_event_subscription
My requirement is not to not set it to 'all' in case event_subscription_source_type is not db-parameter-group. Is there any way to do that?
variable "event_subscription_source_type" {
type = map
description = "Map of the event subscription and event source type"
default = {
"rds-events" = "db-instance"
"db-param-group-chng" = "db-parameter-group"
}
}
#creating rds event subscriptions
resource "aws_db_event_subscription" "event_subscriptions" {
for_each = toset(var.event_subscription_names)
name = each.value
sns_topic = var.sns_topic
source_type = var.event_subscription_source_type[each.value]
source_ids = var.event_subscription_source_type[each.value] != "db-parameter-group" ? null : "all"
...
Error
Error: Incorrect attribute value type
│ on base.tf line 58, in resource "aws_db_event_subscription" "event_subscriptions":
│ 58: source_ids = var.event_subscription_source_type[each.value] != "db-parameter-group" ? null : "all"
│ │ each.value will be known only after apply
│ │ var.event_subscription_source_type is a map of dynamic, known only after apply
│ Inappropriate value for attribute "source_ids": set of string required.

Computed attributes cannot be set : org_id

I am learning how to use Terraform. My aim is to deploy an architecture on GCP so here's my main.tf so far :
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "3.85.0"
}
}
}
provider "google" {
credentials = file(var.credentials_file)
region = var.region
zone = var.zone
}
data "google_organization" "org" {
domain = var.organization.display_name
org_id = var.organization.id
directory_customer_id = var.organization.directory_customer_id
}
resource "google_folder" "shared" {
display_name = "Shared"
parent = google_organization.org_id
}
resource "google_folder" "ddm" {
display_name = "Data and Digital Marketing"
parent = google_folder.shared.name
}
resource "google_folder" "dtl" {
display_name = "DTL"
parent = google_folder.ddm.name
}
According to the documentation, org_id is within the Attributes Reference
But I get the following errors:
╷
│ Error: Computed attributes cannot be set
│
│ with data.google_organization.org,
│ on main.tf line 17, in data "google_organization" "org":
│ 17: org_id = var.organization.id
│
│ Computed attributes cannot be set, but a value was set for "org_id".
╵
╷
│ Error: Computed attributes cannot be set
│
│ with data.google_organization.org,
│ on main.tf line 18, in data "google_organization" "org":
│ 18: directory_customer_id = var.organization.directory_customer_id
│
│ Computed attributes cannot be set, but a value was set for "directory_customer_id".
╵
╷
│ Error: Reference to undeclared resource
│
│ on main.tf line 22, in resource "google_folder" "shared":
│ 22: parent = google_organization.org_id
│
│ A managed resource "google_organization" "org_id" has not been declared in the root module.
What am I doing wrong?
The organization is set as a data source, but in the previous code, it is used like a resource block.
What needs to be done to reference the organization is this :
data "google_organization" "org" {
organization = var.organization.id
}
org_id is an output, not an input. The only acceptable inputs are organization ordomain; they are mutually exclusive.
And use its outputs like this :
resource "google_folder" "shared" {
display_name = "Shared"
parent = data.google_organization.org.org_id
}
EDIT : This, although syntactically correct, it might not work because the account used must be organization administrator on the organization level. I do not recomment using the google_organization data sourcejust to fetch the ID and other info, I ended up writing those in a variable and just calling it this way :
resource "google_folder" "shared" {
display_name = "Shared"
parent = "organizations/${var.organization.id}"
}

Terraform - A reference to resource type must be followed by at least one attribute access, specifying the resource name

I am trying to use terraform string function and string concatenation on a terraform tfvars variable. but when run the terraform plan it through the below exception
Error: A reference to a resource type must be followed by at least one attribute
access, specifying the resource name.
Following is the terraform code
locals {
name_suffix = "${var.namespace != "" ? var.namespace : var.env}"
}
resource "azurerm_container_registry" "my_acr" {
name = "myacr${replace(name_suffix, "-", "")}"
location = "${azurerm_resource_group.location}"
resource_group_name = "${azurerm_resource_group.name}"
sku = "Basic"
admin_enabled = true
}
Here namespace value will be resolved at runtime.
Terraform version 0.12.7
it was a silly mistake. instead of name_suffix, I should have written it like local.name_suffix inside the acr resource
Had a similar issue when setting up Terraform configuration files for AWS Fargate.
Got the error below:
│ Error: Invalid reference
│
│ on ../ecs/main.tf line 72, in resource "aws_ecs_service" "aes":
│ 72: type = order_placement_type
│
│ A reference to a resource type must be followed by at least one attribute access, specifying the resource name.
╵
╷
│ Error: Invalid reference
│
│ on ../ecs/main.tf line 73, in resource "aws_ecs_service" "aes":
│ 73: field = order_placement_field
│
│ A reference to a resource type must be followed by at least one attribute access, specifying the resource name.
The issue was that I missed the var prefix for variables, so instead of this:
ordered_placement_strategy {
type = order_placement_type
field = order_placement_field
}
I corrected it to this:
ordered_placement_strategy {
type = var.order_placement_type
field = var.order_placement_field
}
That's all.
Another thing to check. Make sure you have the index specifier in the correct position.
I had the following code and ran into this problem:
data "cloudflare_origin_ca_root_certificate" "current" {
count = var.domain == null ? 0 : 1
algorithm = tls_private_key.privateKey[0].algorithm
}
resource "aws_acm_certificate" "cert" {
count = var.domain == null ? 0 : 1
#...
certificate_chain = data.cloudflare_origin_ca_root_certificate[0].current.cert_pem
}
Turns out I made the mistake of putting the [0] before the current selector instead of after. So I just had to change the certificate_chain line to the following:
certificate_chain = data.cloudflare_origin_ca_root_certificate.current[0].cert_pem

Resources