How to create a file with terrafom and include variable as literal?
Asked Answered
G

1

8

Using terraform v0.12.9 and building a file using template_file data source, I could not use double dollar signs $$ to treat input ${data_directory} as literals.

Looking for solution to sort out this in a right way or looking for any other suggestion or workaround that can help to create a file with this content.

I have tried to use double dollar sign (like in a code example below) to isolate this ${data_directory} as literals in a file output.

Here is a code that I'm trying to use to create postfix main.cf file with terraform:

variable "hostname" {
  default = "test"
}

variable "domain_name" {
  default = "test.com"
}

variable "fn_main_cf" {
  default = "main.cf"
}

data "template_file" "main_cf" {
  template = <<EOF
##
## Network settings
##
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
inet_interfaces = 127.0.0.1, ::1, 120.121.123.124, 2a03:b0a0:3:d0::5e79:4001
myhostname = ${var.hostname}.${var.domain_name}

###
### Outbound SMTP connections (Postfix as sender)
###
smtp_tls_session_cache_database = btree:$${data_directory}/smtp_scache
EOF
}

  data "template_cloudinit_config" "main_cf" {
    gzip          = false
    base64_encode = false

    part {
      filename     = "${var.fn_main_cf}"
      content_type = "text/cloud-config"
      content      = "${data.template_file.main_cf.rendered}"
    }
  }

  resource "null_resource" "main_cf" {
    triggers = {
      template = "${data.template_file.main_cf.rendered}"
    }

    provisioner "local-exec" {
      command = "echo \"${data.template_file.main_cf.rendered}\" > ~/projects/mail-server/files/etc/postfix/${var.fn_main_cf}"
    }

}

As you can see there is a lot of variables and all this is working fine, but ${data_directory} should not be treated as variable but just as literals and should stay as it is in output file on a disk.

Expected output in the main.cf created file saved on a disk should be like following:

##
## Network settings
##
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
inet_interfaces = 127.0.0.1, ::1, 120.121.123.124, 2a03:b0a0:3:d0::5e79:4001 
myhostname = test.test.com

###
### Outbound SMTP connections (Postfix as sender)
###
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

So ${data_directory} should not be treated by terraform as a terraform variable but just as group of characters, literals (a regular text input).

Running terraform plan the output with double dollar signs $$ is following:

Error: failed to render : <template_file>:11,43-57: Unknown variable; There is no variable named "data_directory".
Guildsman answered 19/9, 2019 at 7:53 Comment(0)
N
10

template_file remains available in Terraform primarily for Terraform 0.11 users. In Terraform 0.12 there is no need to use template_file, because it has been replaced with two other features:

  • For templates in separate files, the built in templatefile function can render an external template from directly in the language, without the need for a separate provider and data source.
  • For inline templates (specified directly within the configuration) you can just write them in directly where they need to be, or factor them out via Local Values.

The local_file resource is also a better way to create a local file on disk than to use a local-exec provisioner. By using template_file and local-exec here you're forcing yourself to contend with two levels of additional escaping: Terraform template escaping to get the literal template into the template_file data source, and then shell escaping inside your provisioner.

Here's a more direct way to represent your template and your file:

variable "postfix_config_path" {
  # Note that for my example this is expected to be the full path
  # to the file, not just the filename. Terraform idiom is to be
  # explicit about this sort of thing, rather than relying on
  # environment variables like HOME.
  type = string
}

locals {
  postfix_config = <<-EOT
    ##
    ## Network settings
    ##
    mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
    inet_interfaces = 127.0.0.1, ::1, 120.121.123.124, 2a03:b0a0:3:d0::5e79:4001
    myhostname = ${var.hostname}.${var.domain_name}

    ###
    ### Outbound SMTP connections (Postfix as sender)
    ###
    smtp_tls_session_cache_database = btree:$${data_directory}/smtp_scache
  EOT
}

resource "local_file" "postfix_config" {
  filename = var.postfix_config_path
  content  = local.postfix_config
}

As the local provider documentation warns, Terraform isn't really designed for directly managing files and other resources on a local machine. The local provider is there for unusual situations, and this might be one of those situations, in which case the above is a reasonable way to address it.

Note though that a more standard Terraform usage pattern would be for Terraform to be used to start up a new virtual machine that will run Postfix and pass in the necessary configuration via a vendor-specific user_data or metadata argument.

If the postfix server is managed separately from this Terraform configuration, then an alternative pattern is to arrange for Terraform to write the necessary data to a shared configuration store (e.g. AWS SSM Parameter Store, or HashiCorp Consul) and then use separate software on the postfix server to read that and update the main.cf file. For HashiCorp Consul, that separate software might be consul-template. Similar software exists for other parameter stores, allowing you to decouple the configuration of individual virtual machines from the configuration of your overall infrastructure.

Namedropper answered 19/9, 2019 at 18:9 Comment(1)
Thank you Martin, great explanation and a guidance how to use Terraform in the right way. :-) Thank you also for explanation about AWS SSM Parameter Store and HashiCorp Consul, I was not aware about those services. I appreciate it very much.Guildsman

© 2022 - 2024 — McMap. All rights reserved.