Skip to content

Commit 51e6c29

Browse files
committed
Add ability to use pongo2 as an alternative to Go Template
refs #177
1 parent cb6223c commit 51e6c29

File tree

10 files changed

+263
-11
lines changed

10 files changed

+263
-11
lines changed

GLOCKFILE

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
github.com/BurntSushi/toml 056c9bc7be7190eaa7715723883caffa5f8fa3e4
22
github.com/fsouza/go-dockerclient e0d22d30691bcc996eca51f729a4777b8c7dc2a8
3+
github.com/flosch/pongo2 1f4be1efe3b3529b7e58861f75d70120a9567dc4

cmd/docker-gen/main.go

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var (
2525
notifySigHUPContainerID string
2626
onlyExposed bool
2727
onlyPublished bool
28+
templateEngine string
2829
includeStopped bool
2930
configFiles stringslice
3031
configs dockergen.ConfigFile
@@ -89,6 +90,7 @@ func initFlags() {
8990
flag.BoolVar(&watch, "watch", false, "watch for container changes")
9091
flag.StringVar(&wait, "wait", "", "minimum and maximum durations to wait (e.g. \"500ms:2s\") before triggering generate")
9192
flag.BoolVar(&onlyExposed, "only-exposed", false, "only include containers with exposed ports")
93+
flag.StringVar(&templateEngine, "engine", "go", "engine used to render templates (\"go\" or \"pongo2\")")
9294

9395
flag.BoolVar(&onlyPublished, "only-published", false,
9496
"only include containers with published ports (implies -only-exposed)")
@@ -138,6 +140,7 @@ func main() {
138140
config := dockergen.Config{
139141
Template: flag.Arg(0),
140142
Dest: flag.Arg(1),
143+
Engine: templateEngine,
141144
Watch: watch,
142145
Wait: w,
143146
NotifyCmd: notifyCmd,

config.go

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
type Config struct {
1212
Template string
1313
Dest string
14+
Engine string
1415
Watch bool
1516
Wait *Wait
1617
NotifyCmd string

example_pongo2.conf

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[[config]]
2+
template = "templates_pongo2/nginx.tmpl"
3+
engine = "pongo2"
4+
dest = "/tmp/nginx.conf"
5+
onlyexposed = true
6+
notifycmd = "/etc/init.d/nginx reload"
7+
8+
[[config]]
9+
template = "templates_pongo2/fluentd.conf.tmpl"
10+
engine = "pongo2"
11+
dest = "/tmp/fluentd.conf"
12+
watch = true
13+
notifycmd = "echo test"
14+
15+
[[config]]
16+
template = "templates_pongo2/etcd.tmpl"
17+
engine = "pongo2"
18+
dest = "/tmp/etcd.sh"
19+
watch = true
20+
notifycmd = "/bin/bash /tmp/etcd.sh"
21+
interval = 10

template.go

+104-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"strings"
1919
"syscall"
2020
"text/template"
21+
22+
"github.com/flosch/pongo2"
2123
)
2224

2325
func exists(path string) (bool, error) {
@@ -453,6 +455,84 @@ func newTemplate(name string) *template.Template {
453455
return tmpl
454456
}
455457

458+
// Takes a template function that returns an error as its second return value,
459+
// and returns a function that takes a pongo2 ExecutionContext as its first
460+
// argument and calls ExecutionContext.OrigError() if the second return value
461+
// of the original function is not nil when called. Otherwise returns the first
462+
// return value.
463+
func pongoWrap(fn interface{}) func(*pongo2.ExecutionContext, ...interface{}) interface{} {
464+
fv := reflect.ValueOf(fn)
465+
ft := reflect.TypeOf(fn)
466+
return func(ctx *pongo2.ExecutionContext, args ...interface{}) interface{} {
467+
if ft.NumIn() != len(args) {
468+
msg := fmt.Sprintf("Wrong number of arguments; expected %d, got %d", ft.NumIn(), len(args))
469+
return ctx.Error(msg, nil)
470+
}
471+
vals := make([]reflect.Value, len(args))
472+
for i, v := range args {
473+
vt := reflect.TypeOf(v)
474+
if !vt.ConvertibleTo(ft.In(i)) {
475+
msg := fmt.Sprintf("Wrong type for argument %d (got %s, expected %s)\n", i, vt, ft.In(i))
476+
return ctx.Error(msg, nil)
477+
}
478+
vals[i] = reflect.ValueOf(args[i])
479+
}
480+
retvals := fv.Call(vals)
481+
ret := retvals[0].Interface()
482+
err := retvals[1].Interface()
483+
if err != nil {
484+
return ctx.OrigError(err.(error), nil)
485+
}
486+
return ret
487+
}
488+
}
489+
490+
func pongoContext(containers Context) pongo2.Context {
491+
context := pongo2.Context{
492+
"containers": containers,
493+
"env": containers.Env,
494+
"docker": containers.Docker,
495+
"closest": arrayClosest,
496+
"coalesce": coalesce,
497+
"contains": contains,
498+
"dict": pongoWrap(dict),
499+
"dir": pongoWrap(dirList),
500+
"exists": pongoWrap(exists),
501+
"first": arrayFirst,
502+
"groupBy": pongoWrap(groupBy),
503+
"groupByKeys": pongoWrap(groupByKeys),
504+
"groupByMulti": pongoWrap(groupByMulti),
505+
"groupByLabel": pongoWrap(groupByLabel),
506+
"hasPrefix": hasPrefix,
507+
"hasSuffix": hasSuffix,
508+
"json": pongoWrap(marshalJson),
509+
"intersect": intersect,
510+
"keys": pongoWrap(keys),
511+
"last": arrayLast,
512+
"replace": strings.Replace,
513+
"parseBool": strconv.ParseBool,
514+
"parseJson": pongoWrap(unmarshalJson),
515+
"printf": fmt.Sprintf,
516+
"queryEscape": url.QueryEscape,
517+
"sha1": hashSha1,
518+
"split": strings.Split,
519+
"splitN": strings.SplitN,
520+
"trimPrefix": trimPrefix,
521+
"trimSuffix": trimSuffix,
522+
"trim": trim,
523+
"when": pongoWrap(when),
524+
"where": pongoWrap(where),
525+
"whereExist": pongoWrap(whereExist),
526+
"whereNotExist": pongoWrap(whereNotExist),
527+
"whereAny": pongoWrap(whereAny),
528+
"whereAll": pongoWrap(whereAll),
529+
"whereLabelExists": pongoWrap(whereLabelExists),
530+
"whereLabelDoesNotExist": pongoWrap(whereLabelDoesNotExist),
531+
"whereLabelValueMatches": pongoWrap(whereLabelValueMatches),
532+
}
533+
return context
534+
}
535+
456536
func filterRunning(config Config, containers Context) Context {
457537
if config.IncludeStopped {
458538
return containers
@@ -486,7 +566,7 @@ func GenerateFile(config Config, containers Context) bool {
486566
filteredContainers = filteredRunningContainers
487567
}
488568

489-
contents := executeTemplate(config.Template, filteredContainers)
569+
contents := executeTemplate(config.Template, config.Engine, filteredContainers)
490570

491571
if !config.KeepBlankLines {
492572
buf := new(bytes.Buffer)
@@ -537,16 +617,29 @@ func GenerateFile(config Config, containers Context) bool {
537617
return true
538618
}
539619

540-
func executeTemplate(templatePath string, containers Context) []byte {
541-
tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePath)
542-
if err != nil {
543-
log.Fatalf("Unable to parse template: %s", err)
544-
}
620+
func executeTemplate(templatePath string, templateEngine string, containers Context) []byte {
621+
if templateEngine == "pongo2" {
622+
context := pongoContext(containers)
623+
tmpl, err := pongo2.FromFile(templatePath)
624+
if err != nil {
625+
log.Fatalf("Unable to parse template: %s", err)
626+
}
627+
contents, err := tmpl.ExecuteBytes(context)
628+
if err != nil {
629+
log.Fatalf("Template error: %s\n", err)
630+
}
631+
return contents
632+
} else {
633+
tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePath)
634+
if err != nil {
635+
log.Fatalf("Unable to parse template: %s", err)
636+
}
545637

546-
buf := new(bytes.Buffer)
547-
err = tmpl.ExecuteTemplate(buf, filepath.Base(templatePath), &containers)
548-
if err != nil {
549-
log.Fatalf("Template error: %s\n", err)
638+
buf := new(bytes.Buffer)
639+
err = tmpl.ExecuteTemplate(buf, filepath.Base(templatePath), &containers)
640+
if err != nil {
641+
log.Fatalf("Template error: %s\n", err)
642+
}
643+
return buf.Bytes()
550644
}
551-
return buf.Bytes()
552645
}
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{% set domain = "docker.company.com" %}
2+
{% for container in containers %}
3+
# {{ container.Name }} ({{ container.ID }} from {{ container.Image.Repository }})
4+
{{ container.IP }} {{ container.Name }}.{{ domain }}
5+
{% if container.IP6Global %}
6+
{{ container.IP6Global }} {{ container.Name }}.{{ domain }}
7+
{% endif %}
8+
{% endfor %}

templates_pongo2/etcd.tmpl

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
# Genenerated by {{ env.USER }}
4+
# Docker Version {{ docker.Version }}
5+
6+
{% for container in containers %}
7+
{% if container.Addresses|length > 0 %}
8+
{% with address = container.Addresses.0 %}
9+
# {{ container.Name }}
10+
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) }}
11+
{% endwith %}
12+
{% endif %}
13+
{% endfor %}

templates_pongo2/fluentd.conf.tmpl

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
## File input
3+
## read docker logs with tag=docker.container
4+
5+
{% for container in containers %}
6+
<source>
7+
type tail
8+
format json
9+
time_key time
10+
path /var/lib/docker/containers/{{ container.ID }}/{{ container.ID }}-json.log
11+
pos_file /var/lib/docker/containers/{{ container.ID }}/{{ container.ID }}-json.log.pos
12+
tag docker.container.{{ printf("%.*s", 12, container.ID) }}
13+
rotate_wait 5
14+
</source>
15+
{% endfor %}
16+
17+
<match docker.**>
18+
type stdout
19+
</match>
20+

templates_pongo2/logrotate.tmpl

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{% for container in containers %}
2+
{% set logs = container.Env.LOG_FILES %}
3+
{% if logs %}
4+
{% for logfile in split(logs, ",") %}
5+
/var/lib/docker/containers/{{ container.ID }}/root{{ logfile }}{% endfor %}
6+
{
7+
daily
8+
missingok
9+
rotate 52
10+
compress
11+
delaycompress
12+
notifempty
13+
create 644 root root
14+
}
15+
{% endif %}
16+
/var/lib/docker/containers/{{ container.ID }}/{{ container.ID }}-json.log
17+
{
18+
daily
19+
missingok
20+
rotate 7
21+
compress
22+
delaycompress
23+
notifempty
24+
create 644 root root
25+
}
26+
{% endfor %}
27+

templates_pongo2/nginx.tmpl

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
server {
2+
listen 80 default_server;
3+
server_name _; # This is just an invalid value which will never trigger on a real hostname.
4+
error_log /proc/self/fd/2;
5+
access_log /proc/self/fd/1;
6+
return 503;
7+
}
8+
9+
{% for host, ctrs in groupByMulti(containers, "Env.VIRTUAL_HOST", ",") %}
10+
11+
upstream {{ host }} {
12+
13+
{% for value in ctrs %}
14+
15+
{% set network = value.Networks.0 %}
16+
17+
{# If only 1 port exposed, use that #}
18+
{% if value.Addresses|length == 1 %}
19+
{% with address = value.Addresses.0 %}
20+
# {{ value.Name }}
21+
server {{ network.IP }}:{{ address.Port }};
22+
{% endwith %}
23+
24+
{# If more than one port exposed, use the one matching VIRTUAL_PORT env var #}
25+
{% elif value.Env.VIRTUAL_PORT %}
26+
{% for address in value.Addresses %}
27+
{% if address.Port == value.Env.VIRTUAL_PORT %}
28+
# {{value.Name}}
29+
server {{ network.IP }}:{{ address.Port }};
30+
{% endif %}
31+
{% endfor %}
32+
33+
{# Else default to standard web port 80 #}
34+
{% else %}
35+
{% for address in value.Addresses %}
36+
{% if address.Port == "80" %}
37+
# {{value.Name}}
38+
server {{ network.IP }}:{{ address.Port }};
39+
{% endif %}
40+
{% endfor %}
41+
{% endif %}
42+
{% endfor %}
43+
}
44+
45+
server {
46+
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
47+
48+
server_name {{ host }};
49+
proxy_buffering off;
50+
error_log /proc/self/fd/2;
51+
access_log /proc/self/fd/1;
52+
53+
location / {
54+
proxy_pass http://{{ trim(host) }};
55+
proxy_set_header Host http_host;
56+
proxy_set_header X-Real-IP remote_addr;
57+
proxy_set_header X-Forwarded-For proxy_add_x_forwarded_for;
58+
proxy_set_header X-Forwarded-Proto scheme;
59+
60+
# HTTP 1.1 support
61+
proxy_http_version 1.1;
62+
proxy_set_header Connection "";
63+
}
64+
}
65+
{% endfor %}

0 commit comments

Comments
 (0)