Rafael Coelho
Rafael Coelho

Reputation: 308

Terraform - Create or not create resources based on conditions

I need my resources to be created on specified environments. For example, if I have a AWS Lambda that is not ready for production, I need it to only exist in development environment. Is there a nice way to do this? I know that it's possible to set count to 0, but I'm not sure how to cascade this decision to other resources.

For example, I have a resource for an AWS Lambda and the count is set to 0.

resource "aws_lambda_function" "example_lambda" {
  count ? local.is_production ? 0 : 1
}

How do I cascade this decision to other resources that depends on the AWS Lambda above?

And let's say I have a S3 Bucket which will invoke the Lambda function.

resource "aws_s3_bucket" "example_bucket" {
  bucket = "bucket_name"
}

resource "aws_lambda_permission" "example_bucket_etl" {
  statement_id  = "AllowExecutionFromS3Bucket"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.example_lambda.arn
  principal     = "s3.amazonaws.com"
  source_arn    = aws_s3_bucket.example_bucket.arn
}

resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket = aws_s3_bucket.example_bucket.id

  lambda_function {
    lambda_function_arn = aws_lambda_function.example_lambda.arn
    events              = ["s3:ObjectCreated:*"]
    filter_prefix       = "example_bucket/"
    filter_suffix       = ".txt"
  
  lambda_function {
    lambda_function_arn = aws_lambda_function.another_lambda_function.arn
    events              = ["s3:ObjectCreated:*"]    
    filter_prefix       = "another_example_bucket/"
    filter_suffix       = ".txt"
  }
}

Upvotes: 2

Views: 3844

Answers (2)

Martin Atkins
Martin Atkins

Reputation: 74084

When you use count in a resource block, that makes Terraform treat references to that resource elsewhere as producing a list of objects representing each of the instances of that resource.

Since that value is just a normal list value, you can take its length in order to concisely write down what is essentially the statement "there should be one Y for each X", or in your specific case "there should be one lambda permission for each lambda function".

For example:

resource "aws_lambda_function" "example" {
  count = local.is_production ? 0 : 1

  # ...
}

resource "aws_lambda_permission" "example_bucket_etl" {
  count = length(aws_lambda_function.example)

  function_name = aws_lambda_function.example[count.index].name
  # ...
}

Inside the aws_lambda_permission configuration we first set the count to be whatever is the count of the aws_lambda_function.example, which tells Terraform that we intend for the counts of these to always match. That connection helps Terraform understand how to resolve situations where you increase or reduce the count, by hinting that the resulting create/destroy actions will need to happen in a particular order in order to be valid. We then use count.index to refer to indices of the other resource, which in this case will only ever be zero but again helps Terraform understand our intent during validation.

The lambda_function nested block inside aws_s3_bucket_notification requires a slightly different strategy, since in that case we're not creating a separate resource instance per lambda function but instead just generating some dynamic configuration blocks inside a single resource instance. For that situation, we can use dynamic blocks which serve as a sort of macro for generating multiple blocks based on elements of a collection:

resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket = aws_s3_bucket.example_bucket.id

  dynamic "lambda_function" {
    for_each = aws_lambda_function.example
    content {
      # "lambda_function" in this block is the iterator
      # symbol, so lambda_function.value refers to the
      # current element of aws_lambda_function.example.
      lambda_function_arn = lambda_function.value.arn
      # ...
    }
  }
}

Again this is relying on the fact that aws_lambda_function.example is a list of objects, but in a different way: we ask Terraform to generate a lambda_function block for each element of aws_lambda_function.example, setting lambda_function.value to the whole aws_lambda_function object corresponding to each block. We can therefore access the .arn attribute from that object to get the corresponding ARN that we need to populate the lambda_function_arn argument inside the block.

Again, for this case there will only ever be zero or one lambda function objects and therefore only zero or one lambda_function blocks, but in both cases this pattern generalizes to other values of count, ensuring that all of these will stay aligned as your configuration evolves.

Upvotes: 3

Tobias Bruckert
Tobias Bruckert

Reputation: 397

You can use the same count variable on multiple resources. A nicer and clear way would be to add all resources into a module, if that is possible in your code. https://www.terraform.io/docs/language/meta-arguments/count.html

Upvotes: 4

Related Questions