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..
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 thegotemplate
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 do something, this is done by this part
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.
References: