hashicorp / hashicorp/terraform
Unable to use the terraform plan to get the relation between different resources before run terraform apply
- Dominant language
- Go
- Stars
- 49.7k
- Forks
- 10.6k
- Avg merge
- 21h 30m
- Merged PRs (30d)
- 100
Description
### Terraform Version
```shell
Terraform v1.2.6
on darwin_amd64
+ provider registry.terraform.io/hashicorp/aws v4.34.0
Your version of Terraform is out of date! The latest version
is 1.3.2. You can update by downloading from https://www.terraform.io/downloads.html
```
### Use Cases
Our team is working on a feature that allow terraform customers to locally test their lambda resources using the [SAM CLI tool](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html).
Our logic is to use the `terraform plan` output to get the Lambda, API Gateway, and StepFunction resources defined in the terraform project and initiate some local docker containers that customer can invoke to simulate a lambda function call locally.
We found some limitations in finding the relation between a Lambda function and lambda layers if the customer does not execute `terraform apply` as the `layers` attribute of the lambda function in the `planned_values` section will be always null as the layers are not created yet.
The use cases we found:
### Multiple instances of the resources definition
If the customer defines multiple instances of a lambda function and also a lambda layer using `count` or `for_each` and use the `count.index` or the `for_each` key to link between these instances, we could not determine which layer instance should be linked to which function instance. See the example below:
```
resource "aws_lambda_layer_version" "layer" {
count = 2
filename = var.layers_source_code[count.index]
layer_name = "lambda_layer${count.index}"
compatible_runtimes = ["python3.8"]
}
resource "aws_lambda_function" "function" {
count = 2
filename = "/Volumes/workplace/tf_poc/investigations/fixtures/get_country_languages.zip"
handler = "index.lambda_handler"
runtime = "python3.8"
function_name = "get_country_languages${count.index}"
role = aws_iam_role.iam_for_lambda.arn
layers = [aws_lambda_layer_version.layer[count.index].arn]
}
```
we should be able to know that there is a relation between function `aws_lambda_function.function[0]` and `aws_lambda_layer_version.layer[0]` and also between `aws_lambda_function.function[1]` and `aws_lambda_layer_version.layer[1]` but actually we could not get this information from the terraform plan.
The `layers` property in the functions instances is null as expected, as the layers are not created yet and there is no ARN generated for them.
```JSON
{
"planned_values": {
"root_module": {
"resources": [
{
"address": "aws_lambda_function.function[0]",
..
"values": {
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/get_country_languages.zip",
"function_name": "get_country_languages0",
"handler": "index.lambda_handler",
...
},
"sensitive_values": {
"layers": [],
...
}
},
{
"address": "aws_lambda_function.function[1]",
...
"values": {
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/get_country_languages.zip",
"function_name": "get_country_languages1",
"handler": "index.lambda_handler",
...
},
"sensitive_values": {
"layers": [],
...
}
},
{
"address": "aws_lambda_layer_version.layer[0]",
...
"values": {
"compatible_runtimes": ["python3.8"],
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/layer1.zip",
"layer_name": "lambda_layer0",
...
}
},
{
"address": "aws_lambda_layer_version.layer[1]",
...
"values": {
"compatible_runtimes": ["python3.8"],
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/layer2.zip",
"layer_name": "lambda_layer1",
....
},
}
]
}
}
}
```
We could not use the `configuration` section as it only contains the resource definition, but does not contain each instance. Also, the `layers` attribute refer to the layer resource configuration and `count.index`
```JSON
{
"configuration": {
...
"root_module": {
"resources": [
{
"address": "aws_lambda_function.function",
...
"expressions": {
...
"layers": {
"references": [
"aws_lambda_layer_version.layer",
"count.index"
]
},
},
"schema_version": 0,
"count_expression": {
"constant_value": 2
}
},
{
"address": "aws_lambda_layer_version.layer",
"expressions": {...}
}
],
}
},
}
```
Even using `terraform graph` command, we found that each lambda function instance has a relation to both layers instances, although it is not correct.
```
"[root] aws_lambda_function.function[0]" -> "[root] aws_lambda_layer_version.layer[0]"
"[root] aws_lambda_function.function[0]" -> "[root] aws_lambda_layer_version.layer[1]"
...
"[root] aws_lambda_function.function[1]" -> "[root] aws_lambda_layer_version.layer[0]"
"[root] aws_lambda_function.function[1]" -> "[root] aws_lambda_layer_version.layer[1]"
```
### Define the relation conditionally
If the customer chooses between multiple lambda layers conditionally to set a lambda function resource layers. We could not determine which layer should be linked to this function. See the example below:
```
resource "aws_lambda_layer_version" "layer1" {
filename = var.layers_source_code[0]
layer_name = "lambda_layer0"
compatible_runtimes = ["python3.8"]
}
resource "aws_lambda_layer_version" "layer2" {
filename = var.layers_source_code[1]
layer_name = "lambda_layer2"
compatible_runtimes = ["python3.8"]
}
resource "aws_lambda_function" "function" {
filename = "/Volumes/workplace/tf_poc/investigations/fixtures/get_country_languages.zip"
handler = "index.lambda_handler"
runtime = "python3.8"
function_name = "function"
role = aws_iam_role.iam_for_lambda.arn
layers = var.env=="prod"? [aws_lambda_layer_version.layer1.arn]:[aws_lambda_layer_version.layer2.arn]
}
```
In case if the `var.env` is `prod`, we should be able to know that there is a relation between function `aws_lambda_function.function` and `aws_lambda_layer_version.layer1` but actually we could not get this information from the terraform plan or terraform graph.
The `layers` property in the functions instances is null as expected, as the layers are not created yet and there is no ARN generated for them.
```JSON
{
"planned_values": {
"root_module": {
"resources": [
{
"address": "aws_lambda_function.function",
"values": {
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/get_country_languages.zip",
"function_name": "function",
"handler": "index.lambda_handler",
...
},
"sensitive_values": {
"layers": [],
...
}
},
{
"address": "aws_lambda_layer_version.layer1",
"values": {
"compatible_runtimes": ["python3.8"],
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/layer1.zip",
"layer_name": "lambda_layer0",
...
},
},
{
"address": "aws_lambda_layer_version.layer2",
"values": {
"compatible_runtimes": ["python3.8"],
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/layer2.zip",
"layer_name": "lambda_layer2",
},
}
]
}
},
}
```
And in the `configuration` section, the Layers attribute is linked to both layers resources which is not correct
```JSON
{
"configuration": {
"root_module": {
"resources": [
{
"address": "aws_lambda_function.function",
...
"expressions": {
...
"layers": {
"references": [
"var.env",
"aws_lambda_layer_version.layer1.arn",
"aws_lambda_layer_version.layer1",
"aws_lambda_layer_version.layer2.arn",
"aws_lambda_layer_version.layer2"
]
},
},
"schema_version": 0
},
{
"address": "aws_lambda_layer_version.layer1",
"expressions": {...},
"schema_version": 0
},
{
"address": "aws_lambda_layer_version.layer2",
"expressions": {...},
"schema_version": 0
}
],
}
},
}
```
Also using the `terraform graph`, the function is linked to both layers:
```
"[root] aws_lambda_function.function" -> "[root] aws_iam_role.iam_for_lambda"
"[root] aws_lambda_function.function" -> "[root] aws_lambda_function.function (expand)"
"[root] aws_lambda_function.function" -> "[root] aws_lambda_layer_version.layer1"
"[root] aws_lambda_function.function" -> "[root] aws_lambda_layer_version.layer2"
"[root] aws_lambda_function.function" -> "[root] var.env"
```
### Attempted Solutions
We could not find a way to solve these limitations.
### Proposal
Is it possible to update the `terraform plan` command to update the plan by following one of the following solutions. To avoid any issues for current users we can make these updates to be enabled by some flag.
#### Solution 1
Is it possible to update the planned value to make the `layers` attribute to refer to the correct layer instance arn as expression instead of string. It can be something like this:
```JSON
{
"planned_values": {
"root_module": {
"resources": [
{
"address": "aws_lambda_function.function[0]",
..
"values": {
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/get_country_languages.zip",
"function_name": "get_country_languages0",
"handler": "index.lambda_handler",
...
},
"expressions":{
"layers": {
"references": [
"aws_lambda_layer_version.layer[0]"
]
},
},
},
{
"address": "aws_lambda_function.function[1]",
...
"values": {
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/get_country_languages.zip",
"function_name": "get_country_languages1",
"handler": "index.lambda_handler",
...
},
"expressions":{
"layers": {
"references": [
"aws_lambda_layer_version.layer[1]"
]
},
},
{
"address": "aws_lambda_layer_version.layer[0]",
...
"values": {
"compatible_runtimes": ["python3.8"],
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/layer1.zip",
"layer_name": "lambda_layer0",
...
}
},
{
"address": "aws_lambda_layer_version.layer[1]",
...
"values": {
"compatible_runtimes": ["python3.8"],
"filename": "/Volumes/workplace/tf_poc/investigations/fixtures/layer2.zip",
"layer_name": "lambda_layer1",
....
},
}
]
}
}
}
```
#### Solution 2:
Can you change the `configuration` section to contain each instance of the resource, and to set the references after doing any required calculations. It can be something like this
```JSON
{
"configuration": {
...
"root_module": {
"resources": [
{
"address": "aws_lambda_function.function[0]",
...
"expressions": {
...
"layers": {
"references": [
"aws_lambda_layer_version.layer[0]"
]
},
},
"schema_version": 0
},
{
"address": "aws_lambda_function.function[1]",
...
"expressions": {
...
"layers": {
"references": [
"aws_lambda_layer_version.layer[1]"
]
},
},
"schema_version": 0
},
{
"address": "aws_lambda_layer_version.layer[0]",
"expressions": {...}
},
{
"address": "aws_lambda_layer_version.layer[1]",
"expressions": {...}
}
],
}
},
}
```
If the previous solutions can not be implemented, is there any library that can help us to parse the terraform project to get all the modules, resources defined in the project and a way to process the expressions and functions.
### References
_No response_
Contributor guide
Research direction
No repository files or tests are named. Start by reviewing the planned_values and configuration examples alongside the terraform graph output, then determine whether instance-level and conditional resource relationships can be represented; done would mean a specified, tested plan behavior that identifies the correct related layer resources.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, go
- Domain
- cloud, devops, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100