Document updated on Aug 28, 2023
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.
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:
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:
{{/* a comment */}}
and can be multiline{{ .variable_name }}
. Notice the starting dot .
.{{ $myvariable := "hello" }}
, and when a variable already exists with {{ $myvariable = "hello2" }}
{{ if CONDITION }}yes{{else}}no{{end}}
. You can also use {{else if}}
. Empty values evaluate to false
.{{ range .ELEMENT}}...{{end}}
or {{ range .ELEMENT}}...{{else}}...{{end}}
. The else
is used when the element you want to iterate is empty. Additionally you can use {{break}}
and {{continue}}
in loops.{{ range $key, $value := .ELEMENT}}...{{end}}
{{with .ELEMENT}}...{{end}}
, or {{with .ELEMENT}}yes{{else}}no{{end}}
when the variable is empty.
(see below)-
, for instance: {{- if true -}}...{{- end -}}
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 stringOnly 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).
$
notation to access outsider contextAs 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.
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.
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.
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.
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"}}"
}
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:
trim
, wrap
, randAlpha
, plural
and more.splitList
, sortAlpha
and more.add
, max
, mul
and more.until
, untilStep
addf
, maxf
, mulf
and more.now
, date
and more.default
, empty
, coalesce
, fromJson
, toJson
, toPrettyJson
, toRawJson
, ternary
b64enc
, b64dec
and more.list
, first
, uniq
and more.get
, set
, dict
, hasKey
, pluck
, dig
, deepCopy
and more.atoi
, int64
, toString
and more.base
, dir
, ext
, clean
, isAbs
, osBase
, osDir
, osExt
, osClean
, osIsAbs
fail
uuidv4
env
, expandenv
semver
, semverCompare
typeOf
, kindIs
, typeIsLike
and more.derivePassword
, sha256sum
, genPrivateKey
and more.getHostByName
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 the file.txt
“as is”. You can use any extension in these files.{{ template "file.tmpl" . }}
: Renders the Go template file.tmpl
passing all its variables as context. The context is the final .
you can see in the block. The file.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, a filename.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.
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.
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.
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 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.
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 }}
]
}
.service.port
is taken from the service.json
file.extra_config
in the third line is inserted as a JSON object using the marshal
function from the service.json
as well.range
iterates the array found under endpoint.json
and key example_group
. The variables inside the range are relative to the example_group
content.include
in the extra_config inserts the content as is.{{if $idx}},{{end}}
inside the loop. When it is not in the first element 0
, 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 }}
.
The documentation is only a piece of the help you can get! Whether you are looking for Open Source or Enterprise support, see more support channels that can help you.