This document provides some tips and guidances for Terraform Engine template developers.
-
Terraform Engine uses go templating under the hood.
-
Use
$
to escape$
in the Terraform Engine templates. (example) -
Use dot (
.
) to obtain mandatory map fields, e.g.{{.name}}
. -
Use
get
function to obtain optional map fields, e.g.{{get . "exists" false}}
. The last argument is the default value to return when the field cannot be found in the map. -
Use
range
function to iterate over a list, e.g.{{range $_, $env := .envs -}} template "env" { recipe_path = "./env.hcl" output_path = "./{{$env}}" data = { env = "{{$env}}" } } {{end}}
-
Check out more available default functions here and custom functions here.
-
When Terraform Engine generates Terraform configs from templates, if the
resource_name
field is not specified for a resource (example), then the underlying Terraform module or Terraform resource in the generated Terraform configs will be automatically named from the resource's unique identifier (bucket's name, network's name, group's id, etc) (example). All non-alphanumeric characters will be converted to_
, such as-
,.
, and@
. -
Members in iam_members component do not support referencing calculated values/attributes from other resources due to an underlying module limitation. A common example is referencing the
email
of a service account created in the same deployment in aniam_members
resource. To work around this limitation, referencing the service account via$${google_service_account.myserviceaccount.account_id}@myproject.iam.gserviceaccount.com
instead of$${google_service_account.myserviceaccount.email}
as theaccount_id
is not a calculated value. (example) -
When writing raw Terraform configs, use
for_each
to group similar resources. -
For large block of raw Terraform configs, instead of using the
terraform_addons.raw_config
block with in atemplate
block, consider using a separatetemplate
block to generate a seperate.tf
file from a template file, e.g. Do not name the.tf
filemain.tf
as it will conflict with the defaultmain.tf
file generated by the Terraform Engine.template "terraform_deployment" { component_path = "./foo.tf.tmpl" output_path = "./foo.tf" data = { ... } }
-
data
maps can be specified either inside or outside atemplate
block.In both cases, values in the
data
maps from an upper level template are passed down to its child template and made available. There is no need to repeat data values in child templates unless you would like to override them. For example, all data values specified in the top level template here are passed down and made available to its child template root.hcl as well as transitive child templates foundation.hcl and team.hcl.However, in the two cases, the
data
maps' value overriding precedence is different, which follows the 3 rules below:- Values specified in the
data
maps inside thetemplate
block take higher precedence over values spcified in thedata
maps outside thetemplate
block. - For
data
maps inside thetemplate
block, values specified in child templates take higher precedence over values specified in parent templates. - For
data
maps outside thetemplate
block, values specified in parent templates take higher precedence over values specified in child templates.
Consider the following example scenario:
# main.hcl data = { bigquery_location = "A" } template "root" { recipe_path = "./modules/root.hcl" data = { bigquery_location = "B" } }
# modules/root.hcl data = { bigquery_location = "C" } # The actual template that consumes bigquery_location. template "bigquery" { recipe_path = "./bigquery.hcl" data = { bigquery_location = "D" } }
# modules/bigquery.hcl resource "google_bigquery_dataset" "dataset" { dataset_id = "example_dataset" location = "{{.bigquery_location}}" }
The 4 locations specified will have the following precedence, from high to low:
D > B > A > C
. And the final value forbigquery_location
will beD
. To explain in detail:- From rule #1,
B
andD
take higher precedence overA
andC
. So(B, D) > (A, C)
. - From rule #2,
D
takes higher precedence overB
. SoD > B > (A, C)
. - From rule #3,
A
takes higher precedence overC
. SoD > B > A > C
.
- Values specified in the
-
Custom schemas with additional variable pattern restrictions can be added to templates to perform custom validation.
-
Deployment order of subfolders in the output is defined by the
managed_dirs
list in thecicd
template (example). CICD jobs will iterate over the list and do plan or apply according to the type of Cloud Build trigger.Note that the
tf-plan
job could fail if one subfolder requires another subfolder to be fully deployed first. For example, deployment of subfolder B has a data dependency on the deployment of subfolder A, and in this case,terraform plan
in the subfolder B will fail until subfolder A is fully deployed. -
Resource dependency is done by using Terraform's implicit dependency mechanism.
Note that Terraform Engine automatically converts all non-alphanumeric characters to
_
when naming the underlying Terraform modules or Terraform resources, so make sure to do that conversion when referencing them. In the example below, the service account's ID iscompute-runner
, but the underlying Terraform resource will be named ascompute_runner
. So when referencing this service account in aniam_member
resource, usegoogle_service_account.compute_runner
with the_
.template "example" { ... data = { resources = { service_accounts = [{ account_id = "compute-runner" }] iam_members = { "roles/storage.objectViewer" = [ "serviceAccount:$${google_service_account.compute_runner.account_id}@my-project.iam.gserviceaccount.com", ] } } } }
-
terraform fmt
is run by default as part of thetfengine
command execution. -
There is no good formatting tools for
.hcl
files. You can use use thehclfmt
command from terragrunt tool but it does not always work. -
{{- ...}}
and{{... -}}
can be used to remove empty lines before or after the current block in the generated configs.
-
When the Terraform Engine template is invalid and the
terraform fmt
step run as part of thetfengine
command will fail, so the output won't get copied over to the output directory. To help debug, pass--format=false
to thetfengine
command and then see the broken file, fix it and then re-run thetfengine
command with--format=true
. -
By default, if you generated a file and then updated your Terraform Engine template to remove the file,
tfengine
won't remove it from the output directory. To delete those unmanaged files, use--delete_unmanaged_files
in thetfengine
command.