4
votes

I'm dynamically creating the following resource in a Terraform v0.12 module:

variables.tf:

variable "stages" {
  type = list(string)
  default = ["v1", "v2"]
}

variable "rest_api_id" {
  description = "The ID of the associated REST API"
}

variable "api_root_resource_id" {
  description = "The API resource ID"
}

variable "region" {
  description = "The AWS region"
}

variable "method" {
  description = "The HTTP method"
  default     = "GET"

variable "lambda" {
  description = "The lambda name to invoke"
}

variable "account_id" {
  description = "The AWS account ID"
}

main.tf

resource "aws_lambda_permission" "lambda_permision" {
  count         = length(var.stages)
  statement_id  = "${var.lambda}${element(var.stages, count.index)}Invoke"
  action        = "lambda:InvokeFunction"
  function_name = "${var.lambda}:${element(var.stages, count.index)}"
  principal     = "apigateway.amazonaws.com"
  source_arn    = "arn:aws:execute-api:${var.region}:${var.account_id}:${var.rest_api_id}/*/${var.method}${aws_api_gateway_resource.api_resource.path}"
}

The inputs don't change. But every apply I receive the following notification:

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # module.signurl_get.aws_lambda_permission.lambda_permision[0] must be replaced
-/+ resource "aws_lambda_permission" "lambda_permision" {
        action        = "lambda:InvokeFunction"
      ~ function_name = "peng_lambda_test_version_eu_dev" -> "peng_lambda_test_version_eu_dev:v1" # forces replacement
      ~ id            = "peng_lambda_test_version_eu_devv1Invoke" -> (known after apply)
        principal     = "apigateway.amazonaws.com"
      - qualifier     = "v1" -> null # forces replacement
        source_arn    = "arn:aws:execute-api:eu-west-1:887428995966:t4m0c9z1uk/*/GET/signurl"
        statement_id  = "peng_lambda_test_version_eu_devv1Invoke"
    }

  # module.signurl_get.aws_lambda_permission.lambda_permision[1] must be replaced
-/+ resource "aws_lambda_permission" "lambda_permision" {
        action        = "lambda:InvokeFunction"
      ~ function_name = "peng_lambda_test_version_eu_dev" -> "peng_lambda_test_version_eu_dev:v2" # forces replacement
      ~ id            = "peng_lambda_test_version_eu_devv2Invoke" -> (known after apply)
        principal     = "apigateway.amazonaws.com"
      - qualifier     = "v2" -> null # forces replacement
        source_arn    = "arn:aws:execute-api:eu-west-1:887428995966:t4m0c9z1uk/*/GET/signurl"
        statement_id  = "peng_lambda_test_version_eu_devv2Invoke"
    }
1

1 Answers

5
votes

When using the aws_lambda_permission resource your function name should be the unqualified Lambda function name. If you need to specify an alias to version your Lambda then this should be done by using the qualifier parameter instead.

Right now Terraform is trying to set the function name to include the qualifier and setting the qualifier to nil. The AWS API happily accepts this and does what you want it to do but then when Terraform refreshes and updates its state it see that the function name has had the qualifier stripped and the qualifier parameter has been set so it attempts to force things back into the way the code tells it should be. Unfortunately this is also an operation that doesn't support an upgrade in place on the Lambda permission resource so it also needs to delete the existing Lambda permission and recreate.

Stripping the qualifier from the function name and adding it in the proper qualifier parameter should fix this:

resource "aws_lambda_permission" "lambda_permision" {
  count         = length(var.stages)
  statement_id  = "${var.lambda}${var.stages[count.index]}Invoke"
  action        = "lambda:InvokeFunction"
  function_name = "${var.lambda}"
  qualifier     = ${var.stages[count.index]}"
  principal     = "apigateway.amazonaws.com"
  source_arn    = "arn:aws:execute-api:${var.region}:${var.account_id}:${var.rest_api_id}/*/${var.method}${aws_api_gateway_resource.api_resource.path}"
}

In the above example I also replaced your element functions with a straight list index with square bracket notation instead. element is useful if you need to loop back through a list multiple times without doing the modulo in the index but otherwise the square bracket notation tends to be slightly more readable and has the same behaviour.

As you mentioned that you're on Terraform 0.12 you can also move to the newer syntax when you aren't concatenating strings and variables as well:

resource "aws_lambda_permission" "lambda_permision" {
  count         = length(var.stages)
  statement_id  = "${var.lambda}${var.stages[count.index]}Invoke"
  action        = "lambda:InvokeFunction"
  function_name = var.lambda
  qualifier     = var.stages[count.index]
  principal     = "apigateway.amazonaws.com"
  source_arn    = "arn:aws:execute-api:${var.region}:${var.account_id}:${var.rest_api_id}/*/${var.method}${aws_api_gateway_resource.api_resource.path}"
}