DEV Community

Alex Leonhardt
Alex Leonhardt

Posted on • Originally published at Medium on

Writing my own terraform template (data) provider

As a follow up to my previous post about how to use terrafrom’s built-in looping mechanisms, I set out to write my own custom “data” provider, in this case a new template renderer that would allow me to use Golang’s text/template instead of terraform’s own.

TL;DR — the code is here..

alex-leonhardt/terraform-provider-gotemplate

I started by trying to follow Hashicorp’s Writing Custom Providers documentation, however that does not seem to explain how to write a data provider , and although I had a basic Hello World working, I didn’t get too close to what I actually wanted to do.

So, since there is an existing template provider, I chose to use that as a kind of starting point and hack my way around all the bits I didn’t want/need. Which probably means I didn’t follow “best practice”. What I wanted was simple, I pass through a json encoded variable to the go-template provider and it will read a template file, render it (or fail), and I should be able to access the result, using the .rendered method, just like the original template provider does.

To use it, it’d roughly look like this …

variable "data" {
 default = {
 "msg" = "Hello World"
 "msg2" = [1, 2, 3, 4]
 }
}

data "gotemplate" "gotmpl" {
template = "${path.module}/file.tmpl"
data = "${jsonencode(var.data)}"
}

output "tmpl" {
value = "${data.gotemplate.gotmpl.rendered}"
}

As you can see, we have a map variable "data" with a mix of values of string type keys, other types are not allowed it seems — see https://stackoverflow.com/a/8758771

I decided, 2 variables should be sufficient, the path to the template file (template) and the json encoded data (data) itself. I probably won’t go into details of how a provider should be structured as TF’s template provider seems a bit different from the “writing custom providers” instructions — probably as it’s a bit older and possibly one of the first. So, since I followed that example (more or less), my new provider also doesn’t follow it, at least not closely.

To use my new provider, all that’s needed is to compile it into a file called terraform-provider-gotemplate and I can use the gotemplate provider, this is specified in the main.go file here

// Provider is a TF provider
func Provider() *schema.Provider {
 return &schema.Provider{
 DataSourcesMap: map[string]*schema.Resource{
 "gotemplate": dataSourceFile(),
 },
 }
}

the important part really is in dataSourceFile() which specifies what variables can be accessed using the provider, in my case that was

  • template (the path to the go template file)
  • data (the json encoded data)
  • rendered (the finished render)

other than setting the schema, I also had to make it actually do something, this is done with

func dataSourceFile() *schema.Resource {
 return &schema.Resource{
 Read: dataSourceFileRead,

which passes dataSourceFileRead function to the schema.Resource‘s Read field (or property? I like to think of it as a property) which must match this function signature

type ReadFunc func(*ResourceData, interface{}) error

which is done with

func dataSourceFileRead(d *schema.ResourceData, meta interface{}) error {
 rendered, err := renderFile(d)
 if err != nil {
 return err
 }
 d.Set("rendered", rendered)
 d.SetId(hash(rendered))
 return nil
}

d.Set is important to set the rendered variable with the finished rendered template, so it can be used with output or other providers as a input.

Before setting rendered we retrieved the rendered output by calling the renderFile(d) function, which looks like this

The final result is available on Github, use it, make PRs if you like, I have learned quite a bit doing this, although still a bit confused and probably not following the standard way of writing providers, but it was fun getting this to work.

alex-leonhardt/terraform-provider-gotemplate

References:


Top comments (0)