Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How can I use a Terraform's modules output as an input to another Terraform module?

I am writing Terraform to deploy an S3 bucket and a lambda. The S3 bucket is needed as this is where the zip object will be stored ready to deploy to lambda.

I have two modules in my top-level main.tf:

module "s3" {
  count  = var.enable_s3 ? 1 : 0
  source = "./modules/s3"
}

module "lambdas" {
  count     = var.enable_lambdas ? 1 : 0
  source    = "./modules/lambdas"
  bucket_id = module.s3.lambda_bucket_id
}

s3 module:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

main.tf

resource "aws_s3_bucket" "lambda_bucket" {
  bucket = "lunchboxd-lambdas"

  tags = {
    Owner       = "Terraform",
    Description = "A bucket to hold the lambdas zip"
  }
}

outputs.tf

output "lambda_bucket_id" {
  value       = aws_s3_bucket.lambda_bucket.id
  description = "ID of the bucket holding the lambda functions."
}

lambdas module:

variables.tf

variable "bucket_id" {}

main.tf

data "archive_file" "lambda_hello_world" {
  type = "zip"

  source_dir  = "${path.module}/hello-world"
  output_path = "${path.module}/hello-world.zip"
}

resource "aws_s3_object" "lambda_hello_world" {
  bucket = var.bucket_id

  key    = "hello-world.zip"
  source = data.archive_file.lambda_hello_world.output_path

  etag = filemd5(data.archive_file.lambda_hello_world.output_path)
}


resource "aws_lambda_function" "hello_world" {
  function_name = "HelloWorld"

  s3_bucket = var.bucket_id
  s3_key    = aws_s3_object.lambda_hello_world.key

  runtime = "nodejs12.x"
  handler = "hello.handler"

  source_code_hash = data.archive_file.lambda_hello_world.output_base64sha256

  role = aws_iam_role.lambda_exec.arn
}

resource "aws_cloudwatch_log_group" "hello_world" {
  name = "/aws/lambda/${aws_lambda_function.hello_world.function_name}"

  retention_in_days = 30
}

resource "aws_iam_role" "lambda_exec" {
  name = "serverless_lambda"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Sid    = ""
      Principal = {
        Service = "lambda.amazonaws.com"
      }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_policy" {
  role       = aws_iam_role.lambda_exec.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

When I try to validate my terraform I am getting the following error:

â•·
│ Error: Unsupported attribute
│ 
│   on main.tf line 36, in module "lambdas":
│   36:   bucket_id = module.s3.aws_s3_bucket.lambda_bucket.id
│     ├────────────────
│     │ module.s3 is a list of object, known only after apply
│ 
│ Can't access attributes on a list of objects. Did you mean to access an attribute for a specific element of the list, or across all elements of the list?
╵

I have followed the documentation on Outputs and module composition but I am unsure what I am doing wrong. All help appreciated.

>Solution :

Your module "s3" block has the count meta-argument and so the documentation under Referring to instances applies here.

Specifically, module.s3 is a list of objects rather than just a single object (as hinted in the error message) and so when you refer to it you need to specify which of the elements of that list you want to refer to.

In your case you only have zero or one instances of the module, and so module.s3 would be a list of either zero or one elements. You’ll therefore need to explain to Terraform what should happen if there are zero elements of module.s3 but one instance of module.lambdas.

If it’s acceptable for the bucket_id variable in module "lambdas" to be null when there is no bucket, one way to write this would be to use the one function to convert the list into either a single value if it has one element or to null if it has no elements:

  bucket_id = one(module.s3.aws_s3_bucket[*].lambda_bucket.id)

This expression first uses the splat operator [*] to translate from the list of objects to a list of just the id values, and then uses one to translate to either a single string or to a null.

If you need bucket_id to have some other value in the case where there are no instances of module.s3 then you’ll need to specify some other logic here to describe how to populate this variable in that case. Exactly what to do will depend on the translation rule you need; you can use any Terraform language expression or function to make a dynamic decision here.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading