Skip to content

Commit

Permalink
Add ability to use pongo2 as an alternative to Go Template
Browse files Browse the repository at this point in the history
refs #177
  • Loading branch information
fdintino committed Oct 1, 2017
1 parent cb6223c commit 51e6c29
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 11 deletions.
1 change: 1 addition & 0 deletions GLOCKFILE
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
github.com/BurntSushi/toml 056c9bc7be7190eaa7715723883caffa5f8fa3e4
github.com/fsouza/go-dockerclient e0d22d30691bcc996eca51f729a4777b8c7dc2a8
github.com/flosch/pongo2 1f4be1efe3b3529b7e58861f75d70120a9567dc4
3 changes: 3 additions & 0 deletions cmd/docker-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var (
notifySigHUPContainerID string
onlyExposed bool
onlyPublished bool
templateEngine string
includeStopped bool
configFiles stringslice
configs dockergen.ConfigFile
Expand Down Expand Up @@ -89,6 +90,7 @@ func initFlags() {
flag.BoolVar(&watch, "watch", false, "watch for container changes")
flag.StringVar(&wait, "wait", "", "minimum and maximum durations to wait (e.g. \"500ms:2s\") before triggering generate")
flag.BoolVar(&onlyExposed, "only-exposed", false, "only include containers with exposed ports")
flag.StringVar(&templateEngine, "engine", "go", "engine used to render templates (\"go\" or \"pongo2\")")

flag.BoolVar(&onlyPublished, "only-published", false,
"only include containers with published ports (implies -only-exposed)")
Expand Down Expand Up @@ -138,6 +140,7 @@ func main() {
config := dockergen.Config{
Template: flag.Arg(0),
Dest: flag.Arg(1),
Engine: templateEngine,
Watch: watch,
Wait: w,
NotifyCmd: notifyCmd,
Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
type Config struct {
Template string
Dest string
Engine string
Watch bool
Wait *Wait
NotifyCmd string
Expand Down
21 changes: 21 additions & 0 deletions example_pongo2.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[[config]]
template = "templates_pongo2/nginx.tmpl"
engine = "pongo2"
dest = "/tmp/nginx.conf"
onlyexposed = true
notifycmd = "/etc/init.d/nginx reload"

[[config]]
template = "templates_pongo2/fluentd.conf.tmpl"
engine = "pongo2"
dest = "/tmp/fluentd.conf"
watch = true
notifycmd = "echo test"

[[config]]
template = "templates_pongo2/etcd.tmpl"
engine = "pongo2"
dest = "/tmp/etcd.sh"
watch = true
notifycmd = "/bin/bash /tmp/etcd.sh"
interval = 10
115 changes: 104 additions & 11 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"strings"
"syscall"
"text/template"

"github.com/flosch/pongo2"
)

func exists(path string) (bool, error) {
Expand Down Expand Up @@ -453,6 +455,84 @@ func newTemplate(name string) *template.Template {
return tmpl
}

// Takes a template function that returns an error as its second return value,
// and returns a function that takes a pongo2 ExecutionContext as its first
// argument and calls ExecutionContext.OrigError() if the second return value
// of the original function is not nil when called. Otherwise returns the first
// return value.
func pongoWrap(fn interface{}) func(*pongo2.ExecutionContext, ...interface{}) interface{} {
fv := reflect.ValueOf(fn)
ft := reflect.TypeOf(fn)
return func(ctx *pongo2.ExecutionContext, args ...interface{}) interface{} {
if ft.NumIn() != len(args) {
msg := fmt.Sprintf("Wrong number of arguments; expected %d, got %d", ft.NumIn(), len(args))
return ctx.Error(msg, nil)
}
vals := make([]reflect.Value, len(args))
for i, v := range args {
vt := reflect.TypeOf(v)
if !vt.ConvertibleTo(ft.In(i)) {
msg := fmt.Sprintf("Wrong type for argument %d (got %s, expected %s)\n", i, vt, ft.In(i))
return ctx.Error(msg, nil)
}
vals[i] = reflect.ValueOf(args[i])
}
retvals := fv.Call(vals)
ret := retvals[0].Interface()
err := retvals[1].Interface()
if err != nil {
return ctx.OrigError(err.(error), nil)
}
return ret
}
}

func pongoContext(containers Context) pongo2.Context {
context := pongo2.Context{
"containers": containers,
"env": containers.Env,
"docker": containers.Docker,
"closest": arrayClosest,
"coalesce": coalesce,
"contains": contains,
"dict": pongoWrap(dict),
"dir": pongoWrap(dirList),
"exists": pongoWrap(exists),
"first": arrayFirst,
"groupBy": pongoWrap(groupBy),
"groupByKeys": pongoWrap(groupByKeys),
"groupByMulti": pongoWrap(groupByMulti),
"groupByLabel": pongoWrap(groupByLabel),
"hasPrefix": hasPrefix,
"hasSuffix": hasSuffix,
"json": pongoWrap(marshalJson),
"intersect": intersect,
"keys": pongoWrap(keys),
"last": arrayLast,
"replace": strings.Replace,
"parseBool": strconv.ParseBool,
"parseJson": pongoWrap(unmarshalJson),
"printf": fmt.Sprintf,
"queryEscape": url.QueryEscape,
"sha1": hashSha1,
"split": strings.Split,
"splitN": strings.SplitN,
"trimPrefix": trimPrefix,
"trimSuffix": trimSuffix,
"trim": trim,
"when": pongoWrap(when),
"where": pongoWrap(where),
"whereExist": pongoWrap(whereExist),
"whereNotExist": pongoWrap(whereNotExist),
"whereAny": pongoWrap(whereAny),
"whereAll": pongoWrap(whereAll),
"whereLabelExists": pongoWrap(whereLabelExists),
"whereLabelDoesNotExist": pongoWrap(whereLabelDoesNotExist),
"whereLabelValueMatches": pongoWrap(whereLabelValueMatches),
}
return context
}

func filterRunning(config Config, containers Context) Context {
if config.IncludeStopped {
return containers
Expand Down Expand Up @@ -486,7 +566,7 @@ func GenerateFile(config Config, containers Context) bool {
filteredContainers = filteredRunningContainers
}

contents := executeTemplate(config.Template, filteredContainers)
contents := executeTemplate(config.Template, config.Engine, filteredContainers)

if !config.KeepBlankLines {
buf := new(bytes.Buffer)
Expand Down Expand Up @@ -537,16 +617,29 @@ func GenerateFile(config Config, containers Context) bool {
return true
}

func executeTemplate(templatePath string, containers Context) []byte {
tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePath)
if err != nil {
log.Fatalf("Unable to parse template: %s", err)
}
func executeTemplate(templatePath string, templateEngine string, containers Context) []byte {
if templateEngine == "pongo2" {
context := pongoContext(containers)
tmpl, err := pongo2.FromFile(templatePath)
if err != nil {
log.Fatalf("Unable to parse template: %s", err)
}
contents, err := tmpl.ExecuteBytes(context)
if err != nil {
log.Fatalf("Template error: %s\n", err)
}
return contents
} else {
tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePath)
if err != nil {
log.Fatalf("Unable to parse template: %s", err)
}

buf := new(bytes.Buffer)
err = tmpl.ExecuteTemplate(buf, filepath.Base(templatePath), &containers)
if err != nil {
log.Fatalf("Template error: %s\n", err)
buf := new(bytes.Buffer)
err = tmpl.ExecuteTemplate(buf, filepath.Base(templatePath), &containers)
if err != nil {
log.Fatalf("Template error: %s\n", err)
}
return buf.Bytes()
}
return buf.Bytes()
}
8 changes: 8 additions & 0 deletions templates_pongo2/dnsmasq.hosts.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% set domain = "docker.company.com" %}
{% for container in containers %}
# {{ container.Name }} ({{ container.ID }} from {{ container.Image.Repository }})
{{ container.IP }} {{ container.Name }}.{{ domain }}
{% if container.IP6Global %}
{{ container.IP6Global }} {{ container.Name }}.{{ domain }}
{% endif %}
{% endfor %}
13 changes: 13 additions & 0 deletions templates_pongo2/etcd.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

# Genenerated by {{ env.USER }}
# Docker Version {{ docker.Version }}

{% for container in containers %}
{% if container.Addresses|length > 0 %}
{% with address = container.Addresses.0 %}
# {{ container.Name }}
curl -XPUT -q -d value="{{ address.IP }}:{{ address.Port }}" -d ttl=15 http://127.0.0.1:4001/v2/keys/backends/{{ container.Image.Repository }}/{{ printf("%.*s", 12, container.ID) }}
{% endwith %}
{% endif %}
{% endfor %}
20 changes: 20 additions & 0 deletions templates_pongo2/fluentd.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

## File input
## read docker logs with tag=docker.container

{% for container in containers %}
<source>
type tail
format json
time_key time
path /var/lib/docker/containers/{{ container.ID }}/{{ container.ID }}-json.log
pos_file /var/lib/docker/containers/{{ container.ID }}/{{ container.ID }}-json.log.pos
tag docker.container.{{ printf("%.*s", 12, container.ID) }}
rotate_wait 5
</source>
{% endfor %}

<match docker.**>
type stdout
</match>

27 changes: 27 additions & 0 deletions templates_pongo2/logrotate.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{% for container in containers %}
{% set logs = container.Env.LOG_FILES %}
{% if logs %}
{% for logfile in split(logs, ",") %}
/var/lib/docker/containers/{{ container.ID }}/root{{ logfile }}{% endfor %}
{
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 root root
}
{% endif %}
/var/lib/docker/containers/{{ container.ID }}/{{ container.ID }}-json.log
{
daily
missingok
rotate 7
compress
delaycompress
notifempty
create 644 root root
}
{% endfor %}

65 changes: 65 additions & 0 deletions templates_pongo2/nginx.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
server {
listen 80 default_server;
server_name _; # This is just an invalid value which will never trigger on a real hostname.
error_log /proc/self/fd/2;
access_log /proc/self/fd/1;
return 503;
}

{% for host, ctrs in groupByMulti(containers, "Env.VIRTUAL_HOST", ",") %}

upstream {{ host }} {

{% for value in ctrs %}

{% set network = value.Networks.0 %}

{# If only 1 port exposed, use that #}
{% if value.Addresses|length == 1 %}
{% with address = value.Addresses.0 %}
# {{ value.Name }}
server {{ network.IP }}:{{ address.Port }};
{% endwith %}

{# If more than one port exposed, use the one matching VIRTUAL_PORT env var #}
{% elif value.Env.VIRTUAL_PORT %}
{% for address in value.Addresses %}
{% if address.Port == value.Env.VIRTUAL_PORT %}
# {{value.Name}}
server {{ network.IP }}:{{ address.Port }};
{% endif %}
{% endfor %}

{# Else default to standard web port 80 #}
{% else %}
{% for address in value.Addresses %}
{% if address.Port == "80" %}
# {{value.Name}}
server {{ network.IP }}:{{ address.Port }};
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
}

server {
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

server_name {{ host }};
proxy_buffering off;
error_log /proc/self/fd/2;
access_log /proc/self/fd/1;

location / {
proxy_pass http://{{ trim(host) }};
proxy_set_header Host http_host;
proxy_set_header X-Real-IP remote_addr;
proxy_set_header X-Forwarded-For proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto scheme;

# HTTP 1.1 support
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
{% endfor %}

0 comments on commit 51e6c29

Please sign in to comment.