Skip to content

Commit c885194

Browse files
committed
Merge pull request #32 from md5/add-functions
Add functions
2 parents e955802 + ac82ce5 commit c885194

File tree

3 files changed

+148
-1
lines changed

3 files changed

+148
-1
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,25 @@ If no `<dest>` file is specified, the output is sent to stdout. Mainly useful f
9595

9696
===
9797

98+
### Templating
99+
100+
The templates used by nginx-proxy are written the Go [text/template](http://golang.org/pkg/text/template/) language. In addition to the [built-in functions](http://golang.org/pkg/text/template/#hdr-Functions) supplied by Go, nginx-proxy provides a number of additional functions to make it simpler (or possible) to generate your desired output.
101+
102+
#### Functions
103+
104+
* *`contains $map $key`*: Returns `true` if `$map` contains `$key`. Takes maps from `string` to `string`.
105+
* *`exists $path`*: Returns `true` if `$path` refers to an existing file or directory. Takes a string.
106+
* *`groupBy $containers $fieldPath`*: Groups an array of `RuntimeContainer` instances based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value, which must be a string. Returns a map from the value of the field path expression to an array of containers having that value. Containers that do not have a value for the field path in question are omitted.
107+
* *`groupByMulti $containers $fieldPath $sep`*: Like `groupBy`, but the string value specified by `$fieldPath` is first split by `$sep` into a list of strings. A container whose `$fieldPath` value contains a list of strings will show up in the map output under each of those strings.
108+
* *`split $string $sep`*: Splits `$string` into a slice of substrings delimited by `$sep`. Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split)
109+
* *`replace $string $old $new $count`*: Replaces up to `$count` occurences of `$old` with `$new` in `$string`. Alias for [`strings.Replace`](http://golang.org/pkg/strings/#Replace)
110+
* *`dict $key $value ...`*: Creates a map from a list of pairs. Each `$key` value must be a `string`, but the `$value` can be any type (or `nil`). Useful for passing more than one value as a pipeline context to subtemplates.
111+
* *`sha1 $string`*: Returns the hexadecimal representation of the SHA1 hash of `$string`.
112+
* *`json $value`*: Returns the JSON representation of `$value` as a `string`.
113+
* *`last $array`*: Returns the last value of an array.
114+
115+
===
116+
98117
### Examples
99118

100119
* [Automated Nginx Reverse Proxy for Docker](http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/)

template.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ package main
22

33
import (
44
"bytes"
5+
"crypto/sha1"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
59
"io"
610
"io/ioutil"
711
"log"
812
"os"
913
"path/filepath"
14+
"reflect"
1015
"strings"
1116
"syscall"
1217
"text/template"
@@ -56,6 +61,41 @@ func contains(item map[string]string, key string) bool {
5661
return false
5762
}
5863

64+
func dict(values ...interface{}) (map[string]interface{}, error) {
65+
if len(values)%2 != 0 {
66+
return nil, errors.New("invalid dict call")
67+
}
68+
dict := make(map[string]interface{}, len(values)/2)
69+
for i := 0; i < len(values); i += 2 {
70+
key, ok := values[i].(string)
71+
if !ok {
72+
return nil, errors.New("dict keys must be strings")
73+
}
74+
dict[key] = values[i+1]
75+
}
76+
return dict, nil
77+
}
78+
79+
func hashSha1(input string) string {
80+
h := sha1.New()
81+
io.WriteString(h, input)
82+
return fmt.Sprintf("%x", h.Sum(nil))
83+
}
84+
85+
func marshalJson(input interface{}) (string, error) {
86+
var buf bytes.Buffer
87+
enc := json.NewEncoder(&buf)
88+
if err := enc.Encode(input); err != nil {
89+
return "", err
90+
}
91+
return strings.TrimSuffix(buf.String(), "\n"), nil
92+
}
93+
94+
func arrayLast(input interface{}) interface{} {
95+
arr := reflect.ValueOf(input)
96+
return arr.Index(arr.Len() - 1).Interface()
97+
}
98+
5999
func generateFile(config Config, containers Context) bool {
60100
templatePath := config.Template
61101
tmpl, err := template.New(filepath.Base(templatePath)).Funcs(template.FuncMap{
@@ -65,6 +105,10 @@ func generateFile(config Config, containers Context) bool {
65105
"groupByMulti": groupByMulti,
66106
"split": strings.Split,
67107
"replace": strings.Replace,
108+
"dict": dict,
109+
"sha1": hashSha1,
110+
"json": marshalJson,
111+
"last": arrayLast,
68112
}).ParseFiles(templatePath)
69113
if err != nil {
70114
log.Fatalf("unable to parse template: %s", err)

template_test.go

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package main
22

3-
import "testing"
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"testing"
7+
)
48

59
func TestContains(t *testing.T) {
610
env := map[string]string{
@@ -99,3 +103,83 @@ func TestGroupByMulti(t *testing.T) {
99103
t.Fatalf("expected 2 got %s", groups["demo3.localhost"][0].ID)
100104
}
101105
}
106+
107+
func TestDict(t *testing.T) {
108+
containers := []*RuntimeContainer{
109+
&RuntimeContainer{
110+
Env: map[string]string{
111+
"VIRTUAL_HOST": "demo1.localhost",
112+
},
113+
ID: "1",
114+
},
115+
&RuntimeContainer{
116+
Env: map[string]string{
117+
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
118+
},
119+
ID: "2",
120+
},
121+
&RuntimeContainer{
122+
Env: map[string]string{
123+
"VIRTUAL_HOST": "demo2.localhost",
124+
},
125+
ID: "3",
126+
},
127+
}
128+
d, err := dict("/", containers)
129+
if err != nil {
130+
t.Fatal(err)
131+
}
132+
if d["/"] == nil {
133+
t.Fatalf("did not find containers in dict: %s", d)
134+
}
135+
if d["MISSING"] != nil {
136+
t.Fail()
137+
}
138+
}
139+
140+
func TestSha1(t *testing.T) {
141+
sum := hashSha1("/path")
142+
if sum != "4f26609ad3f5185faaa9edf1e93aa131e2131352" {
143+
t.Fatal("Incorrect SHA1 sum")
144+
}
145+
}
146+
147+
func TestJson(t *testing.T) {
148+
containers := []*RuntimeContainer{
149+
&RuntimeContainer{
150+
Env: map[string]string{
151+
"VIRTUAL_HOST": "demo1.localhost",
152+
},
153+
ID: "1",
154+
},
155+
&RuntimeContainer{
156+
Env: map[string]string{
157+
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
158+
},
159+
ID: "2",
160+
},
161+
&RuntimeContainer{
162+
Env: map[string]string{
163+
"VIRTUAL_HOST": "demo2.localhost",
164+
},
165+
ID: "3",
166+
},
167+
}
168+
output, err := marshalJson(containers)
169+
if err != nil {
170+
t.Fatal(err)
171+
}
172+
173+
buf := bytes.NewBufferString(output)
174+
dec := json.NewDecoder(buf)
175+
if err != nil {
176+
t.Fatal(err)
177+
}
178+
var decoded []*RuntimeContainer
179+
if err := dec.Decode(&decoded); err != nil {
180+
t.Fatal(err)
181+
}
182+
if len(decoded) != len(containers) {
183+
t.Fatal("Incorrect unmarshaled container count. Expected %d, got %d.", len(containers), len(decoded))
184+
}
185+
}

0 commit comments

Comments
 (0)