Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 57 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

[Terraform](https://www.terraform.io/) is awesome!

As of today, Terraform 0.11 and 0.12 support only static (known, fixed, already computed) values in `tfvars` files. There is no way to use Terraform interpolation functions, or data-sources inside `tfvars` files in Terraform to update values.
Terraform 0.11 and 0.12 supports only static (known, fixed, already computed) values in `tfvars` files. There is no way to use Terraform functions, or data-sources inside `tfvars` files in Terraform to update values.

Using Terraform modules (eg, terraform-aws-modules) developers are not restricted on specific ways how module arguments can be provided (eg, from modules, remote states, other data-sources, command line arguments, tfvars files, etc). Using tfvars files is often a very good idea, because it separates configurations and values! And this is exactly where `tfvars-annotations` tool helps.

While working on [modules.tf](https://github.com/antonbabenko/modules.tf-lambda) (a tool which converts visual diagrams created with [Cloudcraft.co](https://cloudcraft.co/) into Terraform configurations), I had a need to generate code which would chain invocations of [Terraform AWS modules](https://github.com/terraform-aws-modules) and pass arguments between them without requiring any extra Terraform code as a glue. [Terragrunt](https://github.com/gruntwork-io/terragrunt) is a great fit for this, it allows to reduce amount of Terraform configurations by reusing Terraform modules and providing arguments as values in `tfvars` files.

Expand All @@ -23,13 +25,56 @@ Some languages I know have concepts like annotations and decorators, so at first
- [x] terragrunt_output:
- `@tfvars:terragrunt_output.vpc.vpc_id`
- `@tfvars:terragrunt_output.security-group.this_security_group_id`
- [ ] terraform_output
- [x] terraform_output
- `@tfvars:terraform_output.vpc.vpc_id`
- `@tfvars:terraform_output.security-group.this_security_group_id`
- [ ] data-sources generic
1. Type wrapping:
- `to_list`: Wrap original value with `[]` to make it it as a list
- `@tfvars:terragrunt_data.aws_region.zone_id` ?
- `@tfvars:terraform_data.aws_region.zone_id` ?

## General form of the annotations:

`@tfvars:TYPE.DIRECTORY.ATTR[.RETURN_TYPE]`

`TYPE` - Type of the fetcher:
1. terragrunt_output - Get value from `terragrunt output <ATTR>`
1. terraform_output - Get value from `terraform output <ATTR>`
1. terraform_data and terragrunt_data - Get attribute from data-source (eg, `aws_caller_identity.account_id` to ret)

`DIRECTORY` - Working directory where `TYPE` commands should be run. It can be specified in short form if it is a name of directory on the same level as parent directory.
Long form should be used to specify directories on the same level as well as from parent (no max level).

Examples:
1. `[./core]` and `[core]` are identical and refer to `core` subfolder, `[../../../core]` - several folders above
1. `[../core]` and `core` have identical meanings and refer to `core` in the parent directory
1. `[..]` - parent directory
1. `[.]` - current directory

`ATTR` - Name of output to return or name of attribute if `TYPE` is data-source (terraform_data and terragrunt_data).

`RETURN_TYPE` - (Optional) Specify desired type to return. `to_list` can be used to wrap original value with `[]` to make it as a list.

## Examples of various annotations

1. `@tfvars:terragrunt_output.vpc.vpc_id`
1. `@tfvars:terragrunt_output.[../../vpc].vpc_id`
1. `@tfvars:terraform_output.[./vpc].vpc_id.to_list`
1. `@tfvars:terraform_output.[../us-east-1/acm].this_acm_arn`
1. `@tfvars:terragrunt_data.aws_region.zone_id`
1. `@tfvars:terragrunt_data.aws_caller_identity.account_id.to_list`

## How to use

```
Usage: tfvars-annotations [DIR]
-debug
enable debug logging
-version
print version information and exit

[DIR] (optional) - If not specified current directory will be used.
```

Run `tfvars-annotations` before `terraform plan, apply, refresh`.

It will process tfvars file in the current directory and set updated values.
Expand All @@ -38,7 +83,9 @@ E.g.:

$ tfvars-annotations examples/project1-terragrunt/eu-west-1/app
$ terraform plan


## Annotations

## How to disable processing entirely

Put `@tfvars:disable_annotations` anywhere in the `terraform.tfvars` to not process the file.
Expand All @@ -50,8 +97,9 @@ See `examples` for some basics.
## To-do

1. Get values from other sources:
- data sources generic
- data sources generic (with missing values to use the default value)
- aws_account_id or aws_region data sources
- aws_account_id by alias (reference by alias is friendlier)
2. terragrunt_outputs from stacks:
- in any folder
- in current region
Expand All @@ -60,6 +108,8 @@ See `examples` for some basics.
5. rewrite in go (invoke like this => update_dynamic_values_in_tfvars ${get_parent_tfvars_dir()}/${path_relative_to_include()})
6. make it much faster, less verbose
7. add dry-run flag
9. Add -detailed-exitcode to know if files were changed
10. Support terraform.tfvars and terragrunt.hcl or custom list of files
8. Proposed syntax:

- `@tfvars:terragrunt_output.security-group_5.this_security_group_id.to_list`
Expand All @@ -71,6 +121,7 @@ See `examples` for some basics.
- `@tfvars:terragrunt_data.aws_region.zone_id`

- `@tfvars:terragrunt_data.aws_region[{current=true}].zone_id`
// 9. Describe and compare tfvars-annotations vs using remote_state

## Bugs

Expand Down
50 changes: 50 additions & 0 deletions examples/project1-terragrunt/eu-west-1/app/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
terragrunt = {
terraform = {
source = "."
}
}

dependency "core" {
path = "../core"
}

################
# Static values
################

title = "This value is not going to be changed by tfvars-annotations"

#################
# Dynamic values
#################

name = "" # @tfvars:terragrunt_output.core.name

score = "" # @tfvars:terragrunt_output.core.score

name_as_list = [""] # @tfvars:terragrunt_output.core.name.to_list

love_sailing = "" # @tfvars:terragrunt_output.core.love_sailing

understand_how_to_use_twitter = "" # @tfvars:terragrunt_output.core.understand_how_to_use_twitter

languages = "" # @tfvars:terragrunt_output.core.languages

###############
# Compositions
###############

custom_map = {
Score = "" # @tfvars:terragrunt_output.core.score
Name = "" # @tfvars:terragrunt_output.core.name
MixedValue = "" # @ tfvars:terragrunt_output.core.mixed_value <-- same reason as below. Maps are tricky.
}

######
# These don't work yet because there are `maps` inside of them.
######
list_of_properties = "" # @ tfvars:terragrunt_output.core.list_of_properties

map_of_properties = "" # @ tfvars:terragrunt_output.core.map_of_properties

mixed_value = "" # @ tfvars:terragrunt_output.core.mixed_value
7 changes: 0 additions & 7 deletions examples/project1-terragrunt/eu-west-1/core/terraform.tfvars

This file was deleted.

7 changes: 7 additions & 0 deletions examples/project1-terragrunt/eu-west-1/core/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
terraform {
source = "."
}

inputs = {
name = "test"
}
20 changes: 14 additions & 6 deletions examples/project2-terraform/app/terraform.tfvars
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
# @ modulestf:disable_values_updates
# @ tfvars:disable_annotations

vpc_id = "vpc-443a8116aae25c7e9" # @modulestf:terraform_output.vpc.vpc_id
name = "Anton Babenko" # @tfvars:terraform_output.core.name

public_subnets = ["subnet-297e8b509d8aeebfe","subnet-3f831847e5802071c","subnet-6c6f959a9063d32b2"] # @modulestf:terraform_output.vpc.public_subnets
score = "37" # @tfvars:terraform_output.core.score

something = "" # @modulestf:terraform_output.something.id
name_as_list = [""] # @tfvars:terraform_output.core.name.to_list

vpc4_id = "vpc-443a8116aae25c7e9" # @modulestf:terraform_output.vpc.vpc_id
love_sailing = "true" # @tfvars:terraform_output.core.love_sailing

the end!
understand_how_to_use_twitter = "false" # @tfvars:terraform_output.core.understand_how_to_use_twitter

languages = [
"ukrainian",
"russian",
"english",
"norwegian",
"spanish",
] # @tfvars:terraform_output.core.languages
73 changes: 73 additions & 0 deletions examples/project2-terraform/core/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
output "name" {
value = "Anton Babenko"
}

output "score" {
value = "37"
}

output "love_sailing" {
value = "true"
}

output "understand_how_to_use_twitter" {
value = "false"
}

output "languages" {
value = ["ukrainian", "russian", "english", "norwegian", "spanish"]
}

output "map_of_properties" {
value = {
Name = "Anton Babenko"
Age = 34
LoveSailing = true
}
}

output "list_of_properties" {
value = [
{
Name = "Anton Babenko"
Age = 34
LoveSailing = true
UnderstandHowToUseTwitter = false
},
{
Name = "Kapitoshka"
Age = 123
LoveSailing = false
},
]
}

output "mixed_value" {
value = [
"This is just a string",
[
{
Name = "Anton Babenko"
Age = 34
LoveSailing = true
UnderstandHowToUseTwitter = false
},
],
{
Github = "antonbabenko"
Twitter = "antonbabenko"
},
]
}

// Failing values ("=" inside values):
output "failing_values_list" {
value = ["ukrainian = 100", "english", "unknown"]
}

output "failing_values_map" {
value = {
Name = "Anton Babenko"
Age = 34
}
}
108 changes: 108 additions & 0 deletions examples/project2-terraform/core/terraform.tfstate
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"version": 3,
"terraform_version": "0.11.13",
"serial": 1,
"lineage": "71a91e74-7bee-1d3e-0202-e36cd675d7ac",
"modules": [
{
"path": [
"root"
],
"outputs": {
"failing_values_list": {
"sensitive": false,
"type": "list",
"value": [
"ukrainian = 100",
"english",
"unknown"
]
},
"failing_values_map": {
"sensitive": false,
"type": "map",
"value": {
"Age": 34,
"Name": "Anton Babenko"
}
},
"languages": {
"sensitive": false,
"type": "list",
"value": [
"ukrainian",
"russian",
"english",
"norwegian",
"spanish"
]
},
"list_of_properties": {
"sensitive": false,
"type": "list",
"value": [
{
"Age": 34,
"LoveSailing": true,
"Name": "Anton Babenko",
"UnderstandHowToUseTwitter": false
},
{
"Age": 123,
"LoveSailing": false,
"Name": "Kapitoshka"
}
]
},
"love_sailing": {
"sensitive": false,
"type": "string",
"value": "true"
},
"map_of_properties": {
"sensitive": false,
"type": "map",
"value": {
"Age": 34,
"LoveSailing": true,
"Name": "Anton Babenko"
}
},
"mixed_value": {
"sensitive": false,
"type": "list",
"value": [
"This is just a string",
{
"Age": 34,
"LoveSailing": true,
"Name": "Anton Babenko",
"UnderstandHowToUseTwitter": false
},
{
"Github": "antonbabenko",
"Twitter": "antonbabenko"
}
]
},
"name": {
"sensitive": false,
"type": "string",
"value": "Anton Babenko"
},
"score": {
"sensitive": false,
"type": "string",
"value": "37"
},
"understand_how_to_use_twitter": {
"sensitive": false,
"type": "string",
"value": "false"
}
},
"resources": {},
"depends_on": []
}
]
}
Loading