Skip to content

Latest commit

 

History

History
520 lines (379 loc) · 10.8 KB

templates.md

File metadata and controls

520 lines (379 loc) · 10.8 KB

Templates

Templates define a single application configuration template. Templates are stored under the /etc/confd/templates directory by default.

Templates are written in Go's text/template.

Template Functions

map

creates a key-value map of string -> interface{}

{{$endpoint := map "name" "elasticsearch" "private_port" 9200 "public_port" 443}}

name: {{index $endpoint "name"}}
private-port: {{index $endpoint "private_port"}}
public-port: {{index $endpoint "public_port"}}

specifically useful if you a sub-template and you want to pass multiple values to it.

base

Alias for the path.Base function.

{{with get "/key"}}
    key: {{base .Key}}
    value: {{.Value}}
{{end}}

exists

Checks if the key exists. Return false if key is not found.

{{if exists "/key"}}
    value: {{getv "/key"}}
{{end}}

get

Returns the KVPair where key matches its argument. Returns an error if key is not found.

{{with get "/key"}}
    key: {{.Key}}
    value: {{.Value}}
{{end}}

gets

Returns all KVPair, []KVPair, where key matches its argument. Returns an error if key is not found.

{{range gets "/*"}}
    key: {{.Key}}
    value: {{.Value}}
{{end}}

getv

Returns the value as a string where key matches its argument or an optional default value. Returns an error if key is not found and no default value given.

value: {{getv "/key"}}

With a default value

value: {{getv "/key" "default_value"}}

getvs

Returns all values, []string, where key matches its argument. Returns an error if key is not found.

{{range getvs "/*"}}
    value: {{.}}
{{end}}

getenv

Wrapper for os.Getenv. Retrieves the value of the environment variable named by the key. It returns the value, which will be empty if the variable is not present. Optionally, you can give a default value that will be returned if the key is not present.

export HOSTNAME=`hostname`
hostname: {{getenv "HOSTNAME"}}

With a default value

ipaddr: {{getenv "HOST_IP" "127.0.0.1"}}

datetime

Alias for time.Now

# Generated by confd {{datetime}}

Outputs:

# Generated by confd 2015-01-23 13:34:56.093250283 -0800 PST
# Generated by confd {{datetime.Format "Jan 2, 2006 at 3:04pm (MST)"}}

Outputs:

# Generated by confd Jan 23, 2015 at 1:34pm (EST)

See the time package for more usage: http://golang.org/pkg/time/

split

Wrapper for strings.Split. Splits the input string on the separating string and returns a slice of substrings.

{{ $url := split (getv "/deis/service") ":" }}
    host: {{index $url 0}}
    port: {{index $url 1}}

toUpper

Alias for strings.ToUpper Returns uppercased string.

key: {{toUpper "value"}}

toLower

Alias for strings.ToLower. Returns lowercased string.

key: {{toLower "Value"}}

json

Returns an map[string]interface{} of the json value.

lookupSRV

Wrapper for net.LookupSRV. The wrapper also sorts the SRV records alphabetically by combining all the fields of the net.SRV struct to reduce unnecessary config reloads.

{{range lookupSRV "mail" "tcp" "example.com"}}
  target: {{.Target}}
  port: {{.Port}}
  priority: {{.Priority}}
  weight: {{.Weight}}
{{end}}

Add keys to etcd

etcdctl set /services/zookeeper/host1 '{"Id":"host1", "IP":"192.168.10.11"}'
etcdctl set /services/zookeeper/host2 '{"Id":"host2", "IP":"192.168.10.12"}'

Create the template resource

[template]
src = "services.conf.tmpl"
dest = "/tmp/services.conf"
keys = [
  "/services/zookeeper/"
]

Create the template

{{range gets "/services/zookeeper/*"}}
{{$data := json .Value}}
  id: {{$data.Id}}
  ip: {{$data.IP}}
{{end}}

Advanced Map Traversals

Once you have parsed the JSON, it is possible to traverse it with normal Go template functions such as index.

A more advanced structure, like this:

{
  "animals": [
    {"type": "dog", "name": "Fido"},
    {"type": "cat", "name": "Misse"}
  ]
}

It can be traversed like this:

{{$data := json (getv "/test/data/")}}
type: {{ (index $data.animals 1).type }}
name: {{ (index $data.animals 1).name }}
{{range $data.animals}}
{{.name}}
{{end}}

jsonArray

Returns a []interface{} from a json array such as ["a", "b", "c"].

{{range jsonArray (getv "/services/data/")}}
val: {{.}}
{{end}}

ls

Returns all subkeys, []string, where path matches its argument. Returns an empty list if path is not found.

{{range ls "/deis/services"}}
   value: {{.}}
{{end}}

lsdir

Returns all subkeys, []string, where path matches its argument. It only returns subkeys that also have subkeys. Returns an empty list if path is not found.

{{range lsdir "/deis/services"}}
   value: {{.}}
{{end}}

dir

Returns the parent directory of a given key.

{{with dir "/services/data/url"}}
dir: {{.}}
{{end}}

join

Alias for the strings.Join function.

{{$services := getvs "/services/elasticsearch/*"}}
services: {{join $services ","}}

replace

Alias for the strings.Replace function.

{{$backend := getv "/services/backend/nginx"}}
backend = {{replace $backend "-" "_" -1}}

lookupIP

Wrapper for net.LookupIP function. The wrapper also sorts (alphabeticaly) the IP addresses. This is crucial since in dynamic environments DNS servers typically shuffle the addresses linked to domain name. And that would cause unnecessary config reloads.

{{range lookupIP "some.host.local"}}
    server {{.}};
{{end}}

Calculate Functions

Calculate functions only support int64 and float64, so may be overflow, string will auto convert to number, if fail, raise error.

  • add + {{add 3 2}} return 5,{{add "a" 2}} return error
  • div / {{div "3" 2}} return 1, {{div "3.0" 2}} return 1.5
  • mul * {{mul 3 2}} return 6
  • sub - {{sub 3 2}} return 1
  • mod % {{mod 3 2}} return 1
  • max {{max 3 2}} return 3, {{max "3" 2}} return 3
  • min {{min 3 2}} return 2, {{min "3" 2}} return 2

Compare Functions

Compare functions will try convert string to number,then compare number. All number type will cast to float64, so can directly compare int and float. If convert string to number fail, functions will directly compare origin value.

  • eq == {{eq "3" 3}} return true, {{eq 3 3.0}} return true, {{eq "3" "3"}} return true, {{eq 3 "a"}} return false
  • ne != {{ne "3" 3}} return false, {{ne "3" "3"}} return false, {{ne 3 "a"}} return true
  • gt > {{gt "3" 2}} return true, {{gt "3" 2.0 }} return true, {{gt "b" "a"}} return true
  • ge >= {{ge "3" 3}} return true
  • lt < {{lt "2" 3}} return true
  • le <= {{le "3" 3}} return true

filter

Filter string list by regex (if element is not string, just skip).

{{range lsdir "/deis/services" | filter "prefix.*" }}
   value: {{.}}
{{end}}

Filter KVPair list by regex, regex work on KVPair's value.

{{range gets "/deis/services/*" | filter "prefix.*" }}
   key: {{.Key}}
   value: {{.Value}}
{{end}}

toJson

Marshal object to json string

{{$hosts := getvs "/test/data/*/id"}}
{{toJson $hosts}}

toYaml

Marshal object to yaml string

hosts:{{$hosts := getvs "/test/data/*/id"}}
{{toYaml $hosts}}

base64Encode

base64 encoding

{{getv "/test/data/password" |base64Encode }}

base64Decode

base64 decoding

{{getv "/test/data/data" |base64Decode }}

Example Usage

etcdctl set /nginx/domain 'example.com'
etcdctl set /nginx/root '/var/www/example_dotcom'
etcdctl set /nginx/worker_processes '2'
etcdctl set /app/upstream/app1 "10.0.1.100:80"
etcdctl set /app/upstream/app2 "10.0.1.101:80"

/etc/confd/templates/nginx.conf.tmpl

worker_processes {{getv "/nginx/worker_processes"}};

upstream app {
{{range getvs "/app/upstream/*"}}
    server {{.}};
{{end}}
}

server {
    listen 80;
    server_name www.{{getv "/nginx/domain"}};
    access_log /var/log/nginx/{{getv "/nginx/domain"}}.access.log;
    error_log /var/log/nginx/{{getv "/nginx/domain"}}.log;

    location / {
        root              {{getv "/nginx/root"}};
        index             index.html index.htm;
		proxy_pass        http://app;
        proxy_redirect    off;
        proxy_set_header  Host             $host;
        proxy_set_header  X-Real-IP        $remote_addr;
        proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}

Output: /etc/nginx/nginx.conf

worker_processes 2;

upstream app {
    server 10.0.1.100:80;
    server 10.0.1.101:80;
}

server {
    listen 80;
    server_name www.example.com;
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;

    location / {
        root              /var/www/example_dotcom;
        index             index.html index.htm;
        proxy_pass        http://app;
        proxy_redirect    off;
        proxy_set_header  Host             $host;
        proxy_set_header  X-Real-IP        $remote_addr;
        proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}

Complex example

This examples show how to use a combination of the templates functions to do nested iteration.

Add keys to etcd

etcdctl mkdir /services/web/cust1/
etcdctl mkdir /services/web/cust2/
etcdctl set /services/web/cust1/2 '{"IP": "10.0.0.2"}'
etcdctl set /services/web/cust2/2 '{"IP": "10.0.0.4"}'
etcdctl set /services/web/cust2/1 '{"IP": "10.0.0.3"}'
etcdctl set /services/web/cust1/1 '{"IP": "10.0.0.1"}'

Create the template resource

[template]
src = "services.conf.tmpl"
dest = "/tmp/services.conf"
keys = [
  "/services/web"
]

Create the template

{{range $dir := lsdir "/services/web"}}
upstream {{base $dir}} {
    {{$custdir := printf "/services/web/%s/*" $dir}}{{range gets $custdir}}
    server {{$data := json .Value}}{{$data.IP}}:80;
    {{end}}
}

server {
    server_name {{base $dir}}.example.com;
    location / {
        proxy_pass {{base $dir}};
    }
}
{{end}}

Output:/tmp/services.conf

upstream cust1 {
    server 10.0.0.1:80;
    server 10.0.0.2:80;
}

server {
    server_name cust1.example.com;
    location / {
        proxy_pass cust1;
    }
}

upstream cust2 {
    server 10.0.0.3:80;
    server 10.0.0.4:80;
}

server {
    server_name cust2.example.com;
    location / {
        proxy_pass cust2;
    }
}

Go's text/template package is very powerful. For more details on it's capabilities see its documentation.