Skip to content

Add functions #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 31, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,25 @@ If no `<dest>` file is specified, the output is sent to stdout. Mainly useful f

===

### Templating

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.

#### Functions

* *`contains $map $key`*: Returns `true` if `$map` contains `$key`. Takes maps from `string` to `string`.
* *`exists $path`*: Returns `true` if `$path` refers to an existing file or directory. Takes a string.
* *`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.
* *`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.
* *`split $string $sep`*: Splits `$string` into a slice of substrings delimited by `$sep`. Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split)
* *`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)
* *`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.
* *`sha1 $string`*: Returns the hexadecimal representation of the SHA1 hash of `$string`.
* *`json $value`*: Returns the JSON representation of `$value` as a `string`.
* *`last $array`*: Returns the last value of an array.

===

### Examples

* [Automated Nginx Reverse Proxy for Docker](http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/)
Expand Down
44 changes: 44 additions & 0 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ package main

import (
"bytes"
"crypto/sha1"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"syscall"
"text/template"
Expand Down Expand Up @@ -56,6 +61,41 @@ func contains(item map[string]string, key string) bool {
return false
}

func dict(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
}

func hashSha1(input string) string {
h := sha1.New()
io.WriteString(h, input)
return fmt.Sprintf("%x", h.Sum(nil))
}

func marshalJson(input interface{}) (string, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
if err := enc.Encode(input); err != nil {
return "", err
}
return strings.TrimSuffix(buf.String(), "\n"), nil
}

func arrayLast(input interface{}) interface{} {
arr := reflect.ValueOf(input)
return arr.Index(arr.Len() - 1).Interface()
}

func generateFile(config Config, containers Context) bool {
templatePath := config.Template
tmpl, err := template.New(filepath.Base(templatePath)).Funcs(template.FuncMap{
Expand All @@ -65,6 +105,10 @@ func generateFile(config Config, containers Context) bool {
"groupByMulti": groupByMulti,
"split": strings.Split,
"replace": strings.Replace,
"dict": dict,
"sha1": hashSha1,
"json": marshalJson,
"last": arrayLast,
}).ParseFiles(templatePath)
if err != nil {
log.Fatalf("unable to parse template: %s", err)
Expand Down
86 changes: 85 additions & 1 deletion template_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package main

import "testing"
import (
"bytes"
"encoding/json"
"testing"
)

func TestContains(t *testing.T) {
env := map[string]string{
Expand Down Expand Up @@ -99,3 +103,83 @@ func TestGroupByMulti(t *testing.T) {
t.Fatalf("expected 2 got %s", groups["demo3.localhost"][0].ID)
}
}

func TestDict(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
d, err := dict("/", containers)
if err != nil {
t.Fatal(err)
}
if d["/"] == nil {
t.Fatalf("did not find containers in dict: %s", d)
}
if d["MISSING"] != nil {
t.Fail()
}
}

func TestSha1(t *testing.T) {
sum := hashSha1("/path")
if sum != "4f26609ad3f5185faaa9edf1e93aa131e2131352" {
t.Fatal("Incorrect SHA1 sum")
}
}

func TestJson(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
output, err := marshalJson(containers)
if err != nil {
t.Fatal(err)
}

buf := bytes.NewBufferString(output)
dec := json.NewDecoder(buf)
if err != nil {
t.Fatal(err)
}
var decoded []*RuntimeContainer
if err := dec.Decode(&decoded); err != nil {
t.Fatal(err)
}
if len(decoded) != len(containers) {
t.Fatal("Incorrect unmarshaled container count. Expected %d, got %d.", len(containers), len(decoded))
}
}