Template Engine

Runbooks use the GO text/template as the template engine. A runbook is a template to be run against a connection, the placeholders are rendered by inputs provided by an HTTP client.
To define an input, the runbook must be enclosed with {{ }} and the input name must start with a dot. - Example - {{ .myinput }}
The input name must comply with the regular expression \.[a-zA-Z0-9_]+
The input name must comply with the regular expression \.[a-zA-Z0-9_]+

Template Specification

A client could implement input validation based on how templates are created, the specification of inputs are derived from a runbook. The template below:
SELECT name FROM customers WHERE id = '{{ .customer_id }}'
Generates the following specification:
  • GET /api/plugins/runbooks/connections/:dbconn/templates
{ "items": [ { "name": "team/finops/sql/fetch-customer.runbook.sql", "metadata": { "customer_id": { "description": "", "required": false, "type": "text" } } } ], "commit": "b96851b9cf7065f9af0977e506cd1970c60cc87c", "commit_author": "Author <author@domain.tld>", "commit_message": "<commit-message>" }

Supported Fields

The specifications supports the following fields:
  • description - the description of the input
  • type - the type of the input
    • text
    • number
    • tel
    • time
    • date
    • url
    • email
    • select
  • required - if the this field is required
  • default - specifies a default value for an input if it’s empty
The fields indicates how a client could create inputs to a runbook, the fields are defined as function templates in an placeholder using the pipe character |. Example:
plain text
{{ .customer_id | description "the id of the customer" | required "customer_id is required" | type "number" }}

Template Functions

The template engine has auxiliary functions which helps to build better and secure templates:
  • required "<message>" - it will return the error if the input is empty
    • <message> - the message to return when the condition doesn’t match
  • default "<value>" - add a default value to the input if it’s empty
    • <value> - the default value to set
  • pattern "<regexp>" - a regexp pattern to validate the input
    • <regexp> - the go regular expression to validate the input
  • description "<message>" - used as attribute specification to client input validation
    • <message> - the description of the input
  • type "<type>" - used as attribute specification to client input validation
    • <type> - the type of the input (see supported fields for a list of types)
  • placeholder "<message>" - used as attribute specification to client input validation
    • <message> - the description of the placeholder
  • options "<option>" "..." - used as attribute specification to client input validation
    • "<option>" "..." - a list of strings describing each option
  • squote - wraps the input with single quotes: '
  • dquote - wraps the input with double quotes: "
  • quotechar "<char>" - wraps the input with <char>
    • <char> - the character to wrap the input
  • encodeb64 - encode the input as base64
  • decodeb64 - decode a base64 input
  • asenv "<environment>" - add the input as a environment variable
    • <environment> - the name of the environment variable
The functions description and type always returns the value of the last command.


Template functions may be “chained” by separating a sequence of commands with pipeline characters ‘|’. In a chained pipeline, the result of each command is passed as the last argument of the following command. The output of the final command in the pipeline is the value of the pipeline.
The output of a command will be either one value or two values, the second of which has type error. If that second value is present and evaluates to non-nil, the runbook will fail to execute describing what went wrong.
  • Encode the myinput as base64
plain text
myvar = {{ .myinput | encodeb64 }}
  • Wrap the myinput into single quote and encode the input as base64
plain text
myvar = {{ .myinput | squote | encodeb64 }}
  • Gives a description to myinput, encode the value as base64 and then wrap it using the character %
plain text
myvar = {{ .myinput | description "this is my input" | quotechar "%" }}
  • Specify the color as input type select with options red, white and black
plain text
myvar = {{ .color | type "select" | options "red" "white" "black"}}

asenv function

The asenv function allows defining inputs and mapping then as environment variables in the connection runtime. Instead of injecting the value as an input directly to the template, it will gather the value and inject as an environment variable when executing the session.
The inputs could be just defined in a comment in the template, examples:
  • Python Connection Runtime
# {{ .customer_id | asenv "CUSTOMER_ID" }} # {{ .country | asenv "COUNTRY_CODE" }} import os print os.environ['CUSTOMER_ID'] print os.environ['COUNTRY_CODE']
  • Bash Connection Runtime
# {{ .deployment | asenv "DEPLOYMENT_NAME" }} # {{ .namespace | asenv "NAMESPACE" }} kubectl rollout restart deploy/$DEPLOYMENT_NAME -n $NAMESPACE


Server Side Template Injection

Templates are subject to code injection depending on the runtime that you’re using. To mitigate this issue, follows these tips:
  1. Use the pattern function to define the format of the input, specially in sql templates.
SELECT name FROM customers WHERE id = '{{ .customer_id | pattern "^[0-9]+$" }}'
This pattern guarantees that the input will be only a number, this prevents any user to inject any sql instruction
  1. Use the asenv function to expose inputs as environment variables if your runtime supports it
  • This will map the customer_id input as an environment variable avoiding bash injections with shell control operators
# {{ .customer_id | asenv "CUSTOMER_ID" }} /path/to/my/script.sh "$CUSTOMER_ID"
  • The same applies to language runtimes.
# {{ .customer_id | asenv "CUSTOMER_ID" }} import os if __name__ == '__main__': print(os.environ['CUSTOMER_ID'])

Powered by Notaku