DevOps with Go Templates

Templating in programming is common. Over the years, some well known languages have emerged and been adapted by millions.

While some of these templating languages have very specialized use cases, others are excellent general purpose languages. Jinja2 for example has been adopted by Ansible to create dynamic configuration files.

Why you should learn go template syntax

Many modern tools are written in go and allow using templates. The 2 major use cases are dynamic configuration files and formatting output.

Some projects that use go templates are:

  • Docker
  • Kubernetes
  • Helm
  • Hugo
  • Nomad
  • Consul-template
  • Vault
  • Levant
  • Packer

That means when working with modern DevOps tools, sooner or later you will encounter go templates. While there is often times a way around using them, they can enhance the overall workflow drastically.

Getting Started

I would recommend reading go template syntax from the nomad docs.

I have created my own project that uses go templates. Since it renders any JSON input, it can be a useful learning tool.

If you want, you can install it with go get or use the docker image from docker hub.

GitHub logo bluebrown / jpipe

render json with go templates from the command line

jpipe

Render json with go templates from the command line

Docker

curl --silent https://jsonplaceholder.typicode.com/users/1 \
  | docker run --interactive bluebrown/jpipe \
    --newline '{{.name}}'
Enter fullscreen mode Exit fullscreen mode

Local

go get github.com/bluebrown/jpipe
Enter fullscreen mode Exit fullscreen mode

Usage

The templates are executed with the text/template package. That means so they are not injection safe while providing greater flexibility for the user. don't execute untrusted templates!

$ echo '{"place": "bar"}' | jpipe 'lets go to the {{.place}}!'
lets go to the bar!
Enter fullscreen mode Exit fullscreen mode

The template is either read from the first positional argument or from a path specified via --template or -t flag.

echo '{"place": "bar"}' | jpipe --template path/to/template
Enter fullscreen mode Exit fullscreen mode

The json input is read from pipe or redirection.

jpipe < path/to/input.json
curl localhost | jpipe
Enter fullscreen mode Exit fullscreen mode

Flags

-n
-newline
      print new line at the end
-t string
-template string
      alternative way to specify template
Enter fullscreen mode Exit fullscreen mode

Sprig

Sprig functions have been added to provide more…

It takes JSON as input from pipe or redirection and passes the data to a template that is either specified from a file or as positional argument.

By default, the data is rendered in its raw form. It can be an object or array which will become either a map or a slice.

Basic:

# object
$ echo '{ "data": "foo" }' | jpipe
map[data:foo]
# array
$ echo '[1,2]' | jpipe
[1 2]

Passing a template as positional argument:

# object
$ echo '{ "data": "foo" }' | jpipe '{{ .data }}'
foo
# array
$ echo '[1,2]' | jpipe '{{ index . 1 }}'
2

Advanced Syntax

Apart from the basic syntax covered by the nomad docs, below is some more info about templates.

Context

It is important to keep in mind that the . always refers to the current context. The default template actually looks like this:

{{ . }}

Function Call

In go template function are called the same way they are in normal .go files but the parenthesis and commas are stripped away.

So the normal println(item) becomes {{ println . }}. If you have more than 1 argument, then you just add those without a comma.

{{ printf "the size is %f\n"  . }}

Iteration

Often times we want to range over an array or even the keys and values of a map.

When iterating over data, the . will become the item of the current iteration.

{{ range . }} ... {{ end }}'

You can also get the index by assigning it to a variable.

{{ $index, $element := range . }} ... {{ end }}

To iterate over the keys and values of an object, the following syntax is used.

{{ range $key, $val := . }} ... {{ end }}

The Index Function

Sometimes, we need to look up an element in an array by index. The index function takes a slice as first argument and the index of the element to return as second argument.

{{ index .mySlice 2 }}

It is also possible to assign the returned element to a variable and use it somewhere else in the template.

{{ $item := index .mySlice 2 }}{{ $item.title }}

The index function can also lookup keys in a map.

{{ index .myMap "some-key" }}

Example

You can view this example on github.

Below is another example rendering some articles with dev.to liquid tags.

$ curl -s "https://dev.to/api/articles?username=codingsafari&per_page=3" > posts.json
$ jpipe '### {{ println "Posts\n" }}{{range .}}{{ printf "{%% link %s %%}\n"  .url }}{{end}}' < posts.json
Raw Output
### Posts

{% link https://dev.to/codingsafari/building-production-grade-container-images-3nhg %}
{% link https://dev.to/codingsafari/advanced-entrypoint-techniques-for-docker-container-3dc %}
{% link https://dev.to/codingsafari/kubernetes-ingress-controller-gpe %}
Enter fullscreen mode Exit fullscreen mode

18