Search code examples
pythonterraformterraform-provider-aws

The given key does not identify an element in this collection value : terraform


I'm trying to deploy a lambda function using terraform, the code is written in python. Here is the terraform code which performs the operation.

data archive_file UpdateSeqNumInLedgerSummary {
  count       = (var.environment_id == var.prd_environment) ? 1 : 0
  ..
  ..
  ..
  #
}

resource aws_lambda_function update_seqnum_in_indexedledgersummary {
  count            = (var.environment_id == var.prd_environment) ? 1 : 0
  ..
  ..
  ..
  #
}

During deployment I'm also trying to invoke the lambda function using aws_lambda_invocation. Here is what I'm doing

data aws_lambda_invocation update_seqnum_invocation {
  count         = (var.environment_id == var.prd_environment) ? 1 : 0
  function_name = aws_lambda_function.update_seqnum_in_indexedledgersummary[count.index].function_name
  input = <<JSON
  {}
  JSON
}

But during deployent I'm getting the below error

Error: Invalid index

  on seqnum-update.tf line 124, in data "aws_lambda_invocation" "update_seqnum_invocation":
 124:   function_name = aws_lambda_function.update_seqnum_in_indexedledgersummary[count.index].function_name
    |----------------
    | aws_lambda_function.update_seqnum_in_indexedledgersummary is empty tuple
    | count.index is 0

The given key does not identify an element in this collection value.

(The count.index portion of the line 124 snippet is underlined as the relevant subexpression.)

Can someone help me on this?


Solution

  • It seems like the problem here is that Terraform can't infer from the way you've described these relationships that the data.aws_lambda_invocation.update_seqnum_invocation data resource must be read only after making changes to aws_lambda_function.update_seqnum_in_indexedledgersummary.

    It seems that you are using an older version of Terraform, and some older versions of Terraform had less precise dependency inference for relationships between data resources and managed resources, which can lead to problems like this. Upgrading to the latest version of Terraform may therefore make this work more reliably, because modern Terraform includes an additional rule that when a data resource refers to a managed resource and there are planned changes to the managed resource then the data resource must therefore not be read until the apply step.

    To get that sort of effect with older Terraform versions, you can give Terraform some more information so it can better understand your intent.

    First, I'd suggest changing the count for data.aws_lambda_invocation.update_seqnum_invocation so that it's derived from the count of aws_lambda_function.update_seqnum_in_indexedledgersummary, which will therefore allow Terraform to clearly see that those two counts must always change together:

    data "aws_lambda_invocation" "update_seqnum_invocation" {
      count = length(aws_lambda_function.update_seqnum_in_indexedledgersummary)
    
      # ...
    }
    

    The other change is a little harder because it involves finding some way to make the data resource's configuration contain a (known after apply) value whenever the corresponding function doesn't exist yet. Terraform will then use that as a signal that the data resource read must wait until the apply step. However, due to the design of these resource types where aws_lambda_invocation refers to the function by its name and the name is specified directly in your configuration, there doesn't seem to be a natural value to include in the data resource configuration that would have that effect.

    However, we can force this by introducing an extra intermediate resource of type null_resource (which belongs to the hashicorp/null provider) that is guaranteed to produce an unknown id value whenever it's pending creation:

    resource "null_resource" "example" {
      count = length(aws_lambda_function.update_seqnum_in_indexedledgersummary)
    }
    
    data "aws_lambda_invocation" "update_seqnum_invocation" {
      count = length(aws_lambda_function.update_seqnum_in_indexedledgersummary)
    
      # ...
      input = jsonencode({
        irrelevant = null_resource.example.id
      })
    }
    

    An unfortunate part of the above is that it requires adding a useless extra argument to the input of the invocation, because we need to include the unknown ID value somewhere in that configuration. The above will make input be (known after apply) and therefore force reading the data.aws_lambda_invocation.update_seqnum_invocation data resource only during the apply step.


    Above I focused on the details of how to get these parts working together, but I also want to note that what you are doing here is a bit of a misuse of the aws_lambda_invocation data resource, which is a big part of why this is so awkward:

    As with all data sources in Terraform, data "aws_lambda_invocation" is intended for gathering data to use elsewhere in your configuration rather than for describing changes to be made to your infrastructure. Therefore it should typically be used with a function that was defined outside of the current Terraform configuration, which provides some data that the current Terraform configuration needs.

    While it is possible to make Terraform read data resources during the apply step and therefore get the effect of a data resource making an infrastructure change, this sort of approach is inherently fragile and prone to breaking as providers evolve and as Terraform Core itself evolves, because in both cases the goal is generally to gather as much data as possible during the planning phase and so it's considered an improvement to be able to read more data sources during the planning phase rather than the apply phase.

    What exactly this function does isn't in the scope of your question and so I can't suggest a specific alternative, but the general expectation in Terraform is that all changes to infrastructure will be represented by resource blocks rather than by data blocks, and so if you need to modify something as a step in applying this configuration it would be better to either find an existing managed resource type (using a resource block) which can take that action, or to develop a custom Terraform provider that offers the resource type you need. That would then be using Terraform in the way it's designed and so should not break in future versions of providers or of Terraform Core.