Document updated on Aug 28, 2023
Introduction to Templates
There are several components and features in KrakenD that allow you to define configurations or content manipulations using templates.
Whether you are using templates with flexible configuration, a Request generator or Response manipulation the syntax you use is the same, and it’s based on Go templates (as Helm, Kubernetes, and many other systems).
Our convention for saving templates, is using the .tmpl
extension, although this is not enforced. This document provides a few direction to use templates.
Template syntax
The templates use internally the Go text/template
package and Sprig functions. For templates loaded in the Flexible Configuration, there are additional custom functions to load external resources.
There are two inital external documentation pages worth reading to get familiar with these, although you’ll find practical information below:
- The Go text/template documentation sets the rules of templates.
- Sprig functions
Basics of templates
You will recognize templates because their data evaluations or control structures use surrounding {{
and }}
. Any other text outside these delimiters is unprocessed text copied to the output as it is.
For instance, let’s write a simple template krakend.tmpl
(A Go template rendering in JSON format):
{
"version": {{add 2 1}}
}
The template above uses a Sprig function add
that sums two numbers, and prints when executed:
{
"version": 3
}
Templates are agnostic of the encoding you want to express. The example above renders a JSON file, but you could write XML (e.g., when writing a SOAP request) or anything else.
A few basics to get started:
- Comments look like
{{/* a comment */}}
and can be multiline - Variables set by KrakenD are under
{{ .variable_name }}
. Notice the starting dot.
. - Variables you assign can use the syntax
{{ $myvariable := "hello" }}
, and when a variable already exists with{{ $myvariable = "hello2" }}
- Conditionals use the syntax
{{ if CONDITION }}yes{{else}}no{{end}}
. You can also use{{else if}}
. Empty values evaluate tofalse
. - Loops, or iterations use the syntax
{{ range .ELEMENT}}...{{end}}
or{{ range .ELEMENT}}...{{else}}...{{end}}
. Theelse
is used when the element you want to iterate is empty. Additionally you can use{{break}}
and{{continue}}
in loops. - You can loop with assigned indexes and variables as
{{ range $key, $value := .ELEMENT}}...{{end}}
- Access to elements using
{{with .ELEMENT}}...{{end}}
, or{{with .ELEMENT}}yes{{else}}no{{end}}
when the variable is empty - The context is represented with a starting dot
.
(see below) - You can supress preceding and following spaces from any block adding
-
, for instance:{{- if true -}}...{{- end -}}
- You can pipe functions you can add to this
Understanding the context (the dot)
When you execute a template you have an initial data structure available that contains different variables, like the settings files (in a flexible configuration), or the request data (in a request generator) to put a couple of different usages.
The template has access to the data using a period .
and called “dot”. You can print wherever you are the context like this:
{{ toJson . }}
You can access the data structure traversing its keys using the dot operator. For instance, if you have a .var
like {"a": { "b": "hi" } }
you can access the value hi
using {{ .var.a.b }}
.
Variables can be passed as arguments to functions and actions. When you are in a {{ range .var }}
(loop), or use a {{ with .var}}
, you are passing a .var
context. Inside the block you will have a new dot. For instance, considering .var
has the structure above:
{{ with .var.a }}
{{ .b }}
{{end}}
Prints hi
. As you can see the context inside the with is different, and we don’t access it lile .a.b
.
When calling templates from templates (flexible config), make sure to add the final dot .
to pass all the settings files to the next template or pass those variables that are needed:
{{ template "hello.tmpl" . }}
: The hello template receives all setting files and works as its calling template.{{ template "hello.tmpl" .urls.users_api }}
: receives only the string value of the user API.{{ template "hello.tmpl" "hello world" }}
: receives only a constant string
Only in the Enterprise
edition you have an additional variable .meta
holding directory metadata under the settings tree (maybe you want to traverse directory contents in a template).
Using the $
notation to access outsider context
As we have seen, when making a loop with a range
or accessing a with
, the variables inside are relative to its context. Still, you can access outsider variables with the $
notation.
For instance with the same {{.var}}
containing {"a": { "b": "hi" } }
:
{{ with .var.a }}
{{ .b }} and {{ $.var.a.b }}
{{end}}
Prints hi and hi
, because the $
allowed you to get content from outsider its context.
How to separate objects with commas
A lot of times, you need to iterate content and separate it using a comma. To do so, place the comma insertion at the beginning instead of the end when the index in the loop is not zero:
{{ range $index, $endpoint := .endpoints_list }}
{{if $index}},{{end}}
{
"endpoint": "{{ $endpoint.path }}",
"backend": []
}
{{end}}
The {{if $index}},{{end}}
is adding the comma except on the first item because the index 0
does not meet the condition.
Remove white spaces and line breaks
When you use code {{ blocks }}
on your templates, you can add a left dash {{- blocks }}
to suppress preceding whitespaces and linebreaks or a right dash {{ blocks -}}
to remove the following ones, or both {{- blocks -}}
.
Suppressing additional spaces is irrelevant in JSON but not in YAML.
Iterate all files under settings or all keys in a map
If you want to iterate all keys in a map, like the settings files, you must be aware that you need the key names in the first place.
You need to use the keys
function, which returns all the key names in the map, and then you can access its contents using the function index YourMapName yourKeyName
.
For instance, you can dump all settings file contents like this:
{{ range $idx, $setting := keys .}}
{{- if $idx}},{{end -}}
"{{ $setting }}": {{marshal (index $ $setting)}}
{{end}}
In the example above, the range
iterates the key names of .
(not the object itself), which is all the settings in the root template.
Then, the marshal
dumps all the contents provided. The dollar sign $
inside the index represents all your content under .
outside the range. As you are inside a range (a different scope) the .
belongs to the range context, so you need to pass the dollar to access the outsider/parent context.
The index
function gives you access to an element of the map, so index $ $setting
is equivalent to $[$setting]
in other languages.
Inserting an external file as base64
A few fields in KrakenD require you to set their value in base64 format instead of the raw counterpart. For example, sometimes you want to version control the raw file in an external file and reference it as base64. To do so, you could have a template render_as_base64.tmpl
with the following content:
{{/* Notice the dashes (-) at the beginning and end of the following code.
They remove all spaces and linebreaks that appear before and after when the result outputs. */}}
{{- $raw_content := include . -}}
{{- $raw_content | b64enc -}}
And call it in the krakend.tmpl
like this:
{
"version": 3,
"some_base_64_value": "{{template "render_as_base64.tmpl" "file.json"}}"
}
Sprig functions
Complementing the Go built-in template language, Sprig adds more than 100 commonly used functions.
For instance, you could inject secrets from environment variables. Like:
{
"some_secret": "{{ env "SECRET_VARIABLE_NAME" }}"
}
Sprig provides many functions in the following categories:
- String Functions:
trim
,wrap
,randAlpha
,plural
and more.- String List Functions:
splitList
,sortAlpha
and more.
- String List Functions:
- Integer Math Functions:
add
,max
,mul
and more.- Integer Slice Functions:
until
,untilStep
- Integer Slice Functions:
- Float Math Functions:
addf
,maxf
,mulf
and more. - Date Functions:
now
,date
and more. - Defaults Functions:
default
,empty
,coalesce
,fromJson
,toJson
,toPrettyJson
,toRawJson
,ternary
- Encoding Functions:
b64enc
,b64dec
and more. - Lists and List Functions:
list
,first
,uniq
and more. - Dictionaries and Dict Functions:
get
,set
,dict
,hasKey
,pluck
,dig
,deepCopy
and more. - Type Conversion Functions:
atoi
,int64
,toString
and more. - Path and Filepath Functions:
base
,dir
,ext
,clean
,isAbs
,osBase
,osDir
,osExt
,osClean
,osIsAbs
- Flow Control Functions:
fail
- Advanced Functions
- UUID Functions:
uuidv4
- OS Functions:
env
,expandenv
- Version Comparison Functions:
semver
,semverCompare
- Reflection:
typeOf
,kindIs
,typeIsLike
and more. - Cryptographic and Security Functions:
derivePassword
,sha256sum
,genPrivateKey
and more. - Network:
getHostByName
- UUID Functions:
Flexible Configuration custom functions
The following custom functions are available for the flexible configuration, but not in other components.
To load external resources for the templates during runtime (partials, templates, and settings), you reference them in the templates as follows:
{{ include "file.txt" }}
: Inserts the content of thefile.txt
“as is”. You can use any extension in these files.{{ template "file.tmpl" . }}
: Renders the Go templatefile.tmpl
passing all its variables as context. The context is the final.
you can see in the block. Thefile.tmpl
can access this context using{{ . }}
. The context can be a simple value (like a string) or an object/map with nested elements.{{ .setting_name }}
: All the settings files resolve to a tree that you can access in the templates. For instance, afilename.json
is immediately available as the variable{{ .filename }}
in the template.
For instance, having a file settings/urls.json
with the following content:
{
"users_api": "https://users-api.mycompany.com",
"inventory_api": "https://inventory-api.mycompany.com",
"3rdparty": {
"github": "https://api.github.com"
}
}
You can refer to those values in the template like {{ .urls.users_api }}
(which resolves to https://users-api.mycompany.com
). Or you could use nested content like {{ .urls.3rdparty.github }}
and get https://api.github.com
.
As you can see, the first word after the dot is the filename (without the extension), and the following dots traverse the objects to the final value.
Insert structures from settings files
When instead of a single value, you need to insert a JSON structure (several elements), you need to use marshal
.
{{ marshal .urls }}
You can dump the context anywhere like this (useful for debugging):
{{ . | marshal }}
The example would write the entire content of the urls.json
file.
Include a partial
To insert the content of an external partial file in place use:
{{ include "dir1/dir2/partial_file_name.txt" }}
The content inside the partial template is not parsed and is inserted as is in plain text. The file is assumed to live inside the directory defined as partials and can have any name and extension. Filenames referenced are case sensitive, and although your host operating system might work with case insensitive files (e.g., A docker volume on Mac) when copied to a Docker image not respecting the case will fail.
Include and process a sub-template
While the include
is only meant to paste the content of a plain text file, the template
gives you all the power of Go templating. The syntax is as follows:
{{ template "template_name.tmpl" .some_context }}
The template template_name.tmpl
is executed and processed. The depicted variable .some_context
is passed in the template as the context.
The context
The context is data you pass to a template as if it were a single parameter of a function.
When the base template loads (e.g., the krakend.tmpl
), it automatically receives in the context the whole settings tree. It means that if you dump the content of the context, you will see the entire tree made of all files and data structures in the settings dir.
If you want to pass all the settings tree to the calling template, write just a dot .
, which stands for “everything”. For instance:
{{ template "template_name.tmpl" . }}
The called template can see the object you passed as the context under {{ . }}
. In this case, all the settings are available as in the base template. But sometimes templates need a smaller object, a constant string, or a number as a parameter. You can also do this, for instance:
{{ template "subtemplate1.tmpl" .some_setting.some_value }}
{{ template "ratelimit_per_minute.tmpl" 100 }}
Go templates allow you to introduce handy stuff like conditionals or loops and create powerful configurations.
Example file for flexible config
There is a lot of theory so far. To demonstrate the usage of the flexible configuration, we will reorganize a configuration file into several pieces. This is a simple example to see the basics of the templates system:
.
├── krakend.tmpl
├── partials
│ └── all_backends_extra_config.json
└── settings
├── endpoint.json
└── service.json
partials/all_backends_extra_config.json
In this file, we have written the content of the rate limit configuration and circuit breaker we want for any backend. This file is inserted when included “as is”, because it is a partial:
{
"$schema": "https://www.krakend.io/schema/v2.4/backend_extra_config.json",
"qos/ratelimit/proxy": {
"max_rate": 100,
"capacity": 100
},
"qos/circuit-breaker": {
"interval": 10,
"max_errors": 5,
"timeout": 5
}
}
settings/service.json
In the settings directory, we write all the files whose values can be accessed as variables.
{
"port": 8090,
"default_hosts": [
"https://catalog-api-01.srv",
"https://catalog-api-02.srv",
"https://catalog-api-03.srv"
],
"extra_config": {
"security/http": {
"allowed_hosts": [],
"ssl_proxy_headers": {
"X-Forwarded-Proto": "https"
},
"ssl_certificate": "/opt/rsa.cert",
"ssl_private_key": "/opt/rsa.key"
}
}
}
settings/endpoint.json
This file declares a couple of endpoints that feed on a single backend:
{
"example_group": [
{
"endpoint": "/users/{id}",
"backend": "/v1/users?userId={id}"
},
{
"endpoint": "/posts/{id}",
"backend": "/posts?postId={id}"
}
]
}
krakend.tmpl
Finally, let’s introduce the base template. It inserts the content of other files using include
, uses the variables declared in the settings files, and writes json content with marshal
.
Have a look at the highlighted lines:
{
"version": 3,
"port": {{ .service.port }},
"extra_config": {{ marshal .service.extra_config }},
"host": {{ marshal .service.default_hosts }},
"endpoints": [
{{ range $idx, $endpoint := .endpoint.example_group }}
{{if $idx}},{{end}}
{
"endpoint": "{{ $endpoint.endpoint }}",
"backend": [
{
"url_pattern": "{{ $endpoint.backend }}",
"extra_config": {{ include "all_backends_extra_config.json" }}
}
]}
{{ end }}
]
}
- The
.service.port
is taken from theservice.json
file. - The
extra_config
in the third line is inserted as a JSON object using themarshal
function from theservice.json
as well. - A
range
iterates the array found underendpoint.json
and keyexample_group
. The variables inside the range are relative to theexample_group
content. - An
include
in the extra_config inserts the content as is. - Also notice the little trick
{{if $idx}},{{end}}
inside the loop. When it is not in the first element0
, it will add a comma to prevent breaking the JSON format.
Notice that there is a {{ range }}
. If you wanted to use it inside a template and not the base file, you would need to include it inside a sub-template with {{ template "template.tmp" .endpoint.example_group }}
.