skip to Main Content

I’m trying to write up some terraform code which will create a function and preferably create a new execution role, however the user does have the option of attaching an existing role.

variable "create_role" {
  description = "Controls whether IAM role for Lambda Function should be created"
  type        = bool
  default     = true
}

variable "lambda_role_arn" {
  description = " IAM role ARN attached to the Lambda Function. This governs both who / what can invoke your Lambda Function, as well as what resources our Lambda Function has access to. See Lambda Permission Model for more details."
  type        = string
}

resource "aws_lambda_function" "lambda_function" {
    function_name                   = function_name
    role                            = var.create_role ? aws_iam_role.lambda[0].arn : var.lambda_role_arn

The drawback with this method is that there is a chance of create_role = false and lambda_role_arn being undefined.

If there a method of only allowing lambda_role_arn to be null IF create_role is true?

Thanks in advance.

2

Answers


  1. In this situation the objective can be achieved with the try function:

    role = try(aws_iam_role.lambda[0].arn, var.lambda_role_arn, null)
    

    If the first value can be successfully resolved (corresponding to the managed role), then it will be returned and assigned. Otherwise the next variadic argument will be returned and assigned (corresponding to the input variable). If neither can be resolved (signifying role not managed nor input), then the null type will be returned and assigned as desired (effectively ignoring the parameter value).

    Note the coaslece function that provides analogous null-coaslescing functionality cannot be used as it does not permit returning null.

    Login or Signup to reply.
  2. One way to solve this would be to prevent the invalid situation from occurring in the first place by making a single variable which controls both of these behaviors together. This sort of design is also often easier for users of a module to understand, because they can see everything related to a particular behavior all in one place.

    Based on your requirements, I would probably design this so that the module declares its own role only if the caller doesn’t provide one:

    variable "lambda_role_arn" {
      type    = string
      default = null
    
      description = "The ARN of a pre-existing role to use for the Lambda function. If unset, the module will declare a new role to use."
    }
    
    resource "aws_iam_role" "lambda" {
      count = var.lambda_role_arn == null ? 1 : 0
    
      # ...
    }
    
    locals {
      # This is either the caller's provided ARN or
      # the ARN of the inline-declared role.
      final_role_arn = coalesce(
        var.lambda_role_arn,
        aws_iam_role.lambda[*].arn...
      )
    }
    
    resource "aws_lambda_function" "lambda_function" {
      function_name = function_name
      role          = local.final_role_arn
      # ...
    }
    

    The idea of there being an automatic default behavior when an argument is left unset is a pretty common situation in Terraform — lots of resource type arguments work like that, for example — so a design like the above fits in with typical Terraform design idiom: either specify an explicit lambda_role_arn in the caller’s module block, or omit it to get the default one.

    However, in some situations that might be considered a little too "magical". For example, it might be true that for this particular module the common case is to pass in a role while having it automatically declared inline is the unusual case that must be explicitly selected. If that were true then I would choose a slightly different approach where the user of the module must specify how the role ARN is to be assigned, but can choose either option:

    variable "lambda_role" {
      type = object({
        arn       = optional(string)
        automatic = optional(bool, false)
      })
      nullable = false
    
      validation {
        condition = (
          (var.lambda_role.arn != null && !var.automatic) ||
          (var.lambda_role.arn == null && var.automatic)
        )
        error_message = "Must set either the "arn" or "automatic" attribute, but not both."
      }
    }
    
    resource "aws_iam_role" "lambda" {
      count = var.lambda_role.automatic ? 1 : 0
    
      # ...
    }
    
    locals {
      # This is either the caller's provided ARN or
      # the ARN of the inline-declared role.
      final_role_arn = coalesce(
        var.lambda_role.arn,
        aws_iam_role.lambda[*].arn...
      )
    }
    
    
    resource "aws_lambda_function" "lambda_function" {
      function_name = function_name
      role          = local.final_role_arn
      # ...
    }
    

    In this variation the caller of the module must choose between one of the two possible modes; omitting the argument altogether is invalid:

      lambda_role = {
        arn = "..."
      }
    
      lambda_role = {
        automatic = true
      }
    

    (Side note: I called the boolean attribute "automatic" instead of "create" because it’s a bit of a misnomer to say that the module "creates" the role. Instead, the module declares the role and then on the first terraform apply Terraform will plan to create it, but after that the role will already exist and so the module will just continue to manage it in whatever way needed to keep the remote object matching the changes to the configuration over time. Not a big deal if you do still want to call it create, but I find that tends to confuse some folks new to Terraform who aren’t yet accustomed to its declarative programming model.)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search