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
getfunction 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
rangefunction 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_namefield 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
emailof a service account created in the same deployment in aniam_membersresource. To work around this limitation, referencing the service account via$${google_service_account.myserviceaccount.account_id}@myproject.iam.gserviceaccount.cominstead of$${google_service_account.myserviceaccount.email}as theaccount_idis not a calculated value. (example) -
When writing raw Terraform configs, use
for_eachto group similar resources. -
For large block of raw Terraform configs, instead of using the
terraform_addons.raw_configblock with in atemplateblock, consider using a separatetemplateblock to generate a seperate.tffile from a template file, e.g. Do not name the.tffilemain.tfas it will conflict with the defaultmain.tffile generated by the Terraform Engine.template "terraform_deployment" { component_path = "./foo.tf.tmpl" output_path = "./foo.tf" data = { ... } }
-
datamaps can be specified either inside or outside atemplateblock.In both cases, values in the
datamaps 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
datamaps' value overriding precedence is different, which follows the 3 rules below:- Values specified in the
datamaps inside thetemplateblock take higher precedence over values spcified in thedatamaps outside thetemplateblock. - For
datamaps inside thetemplateblock, values specified in child templates take higher precedence over values specified in parent templates. - For
datamaps outside thetemplateblock, 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_locationwill beD. To explain in detail:- From rule #1,
BandDtake higher precedence overAandC. So(B, D) > (A, C). - From rule #2,
Dtakes higher precedence overB. SoD > B > (A, C). - From rule #3,
Atakes 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_dirslist in thecicdtemplate (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-planjob 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 planin 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_memberresource, usegoogle_service_account.compute_runnerwith 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 fmtis run by default as part of thetfenginecommand execution. -
There is no good formatting tools for
.hclfiles. You can use use thehclfmtcommand 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 fmtstep run as part of thetfenginecommand will fail, so the output won't get copied over to the output directory. To help debug, pass--format=falseto thetfenginecommand and then see the broken file, fix it and then re-run thetfenginecommand with--format=true. -
By default, if you generated a file and then updated your Terraform Engine template to remove the file,
tfenginewon't remove it from the output directory. To delete those unmanaged files, use--delete_unmanaged_filesin thetfenginecommand.