0

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

  • The error message seems to be saying that `data.aws_s3_object.tdjson.body` is an empty string, which is therefore not valid JSON. Are you sure that the object in your S3 bucket contains valid JSON syntax? – Martin Atkins Jun 08 '22 at 18:45
  • I've double checked the .json file with a json linter and it's ok. As I mentioned, if I hardcode the s3 object name in the 'key' value in the data source it works fine. – user19300352 Jun 09 '22 at 08:21
  • Can you add `data.aws_s3_objects.serviceList.keys` to the question? – Marko E Jun 09 '22 at 11:13
  • Added the data source for data.aws_s3_objects.serviceList.keys to the question – user19300352 Jun 09 '22 at 12:38
  • If you were to try this in the root module: `locals { s3_keys = toset(data.aws_s3_objects.serviceList.keys) }` and then in the module call: `for_each = local.s3_keys`? – Marko E Jun 10 '22 at 09:17

1 Answers1

0

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.

Jordan
  • 3,998
  • 9
  • 45
  • 81
  • Hi I already ran into and fixed that problem. The objects in the bucket all have the Content-type set to application/json and indeed the error is not thrown when set the key value in the module to a string rather than a variable. – user19300352 Jun 09 '22 at 13:47
  • To check if the key names were being passed into the module successfully I tried removing the jsondecode function from the local so that it looked like `servicedef = data.aws_s3_object.tdjson.body` It threw an error but I could see that the raw json for each object in the error messages so it looked like the key names were coming through from main. – user19300352 Jun 09 '22 at 13:57
  • I found the issue, check the edit on my answer. – Jordan Jun 09 '22 at 15:39
  • I've changed it to read as below but , this doesn't make any difference and the error is still the same. `module "API_test" { for_each = toset(data.aws_s3_objects.serviceList.keys) source = "./modules/Synthetics" S3key = each.key }` – user19300352 Jun 09 '22 at 16:00
  • Can you post the output of `terraform plan` in the question? – Jordan Jun 09 '22 at 16:05
  • Hi, thanks for help. While reading through the plan output I spotted that some of the .json file appeared to being read. I realised that one of them was empty as I'd created it only as a dummy to check the object name was read. I deleted that file and then the plan went through. For info. here is the plan output when failing: – user19300352 Jun 09 '22 at 16:41
  • `tf plan datadog_monitor.foo: Refreshing state... [id=6041224] data.aws_s3_objects.serviceList: Reading... data.aws_s3_objects.serviceList: Read complete after 0s [id=testingbucket] module.API_test["AccountsStack.json"].data.aws_s3_object.tdjson: Reading...` – user19300352 Jun 09 '22 at 16:42
  • `module.API_test["ManagementStack.json"].data.aws_s3_object.tdjson: Reading... module.API_test["Datastore.json"].data.aws_s3_object.tdjson: Reading... module.API_test.datadog_synthetics_test.GetToken: Refreshing state... [id=v9a-4ek-djn] module.API_test["OrderExecution.json"].data.aws_s3_object.tdjson: Reading... module.API_test["ManagementStack.json"].data.aws_s3_object.tdjson: Read complete after 0s [id=testingbucket/ManagementStack.json]` – user19300352 Jun 09 '22 at 16:42
  • `module.API_test["Datastore.json"].data.aws_s3_object.tdjson: Read complete after 0s [id=testingbucket/Datastore.json] module.API_test["OrderExecution.json"].data.aws_s3_object.tdjson: Read complete after 0s [id=testingbucket/OrderExecution.json] module.API_test.datadog_synthetics_global_variable.OHcogAuthToken: Refreshing state... [id=69c314db-bd35-48db-b20c-74f90c1ad8a3] module.API_test["AccountsStack.json"].data.aws_s3_object.tdjson: Read complete after 0s [id=testingbucket/AccountsStack.json] datadog_dashboard.multidash["AccountsStack.json"]: Refreshing state... [id=539-4u9-ied]` – user19300352 Jun 09 '22 at 16:43
  • `datadog_dashboard.multidash["OrderExecution.json"]: Refreshing state... [id=22a-9f4-bfw] datadog_dashboard.multidash["Datastore.json"]: Refreshing state... [id=ci7-k4s-n49] datadog_dashboard.multidash["ManagementStack.json"]: Refreshing state... [id=d9n-tq9-rgw] module.API_test.datadog_synthetics_test.QA_TD_EXTRACTTRADES: Refreshing state... [id=rp7-q7g-w24]` – user19300352 Jun 09 '22 at 16:43
  • `╷ │ 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.` – user19300352 Jun 09 '22 at 16:44
  • @Jordan Sorry to interject, but keys and values are the same when using sets: https://www.terraform.io/language/meta-arguments/for_each#each-value. – Marko E Jun 10 '22 at 09:07