Skip to content

refactor: cleanup template functions #624

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 5 commits into from
Jul 31, 2024
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
34 changes: 24 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,23 +398,13 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
- _`groupByLabelWithDefault $containers $label $defaultValue`_: Returns the same as `groupBy` but grouping by the given label's value. Containers that do not have the `$label` set are included in the map under the `$defaultValue` key.
- _`include $file`_: Returns content of `$file`, and empty string if file reading error.
- _`intersect $slice1 $slice2`_: Returns the strings that exist in both string slices.
- _`json $value`_: Returns the JSON representation of `$value` as a `string`.
- _`fromYaml $string` / `mustFromYaml $string`_: Similar to [Sprig's `fromJson` / `mustFromJson`](https://github.com/Masterminds/sprig/blob/master/docs/defaults.md#fromjson-mustfromjson), but for YAML.
- _`toYaml $dict` / `mustToYaml $dict`_: Similar to [Sprig's `toJson` / `mustToJson`](https://github.com/Masterminds/sprig/blob/master/docs/defaults.md#tojson-musttojson), but for YAML.
- _`keys $map`_: Returns the keys from `$map`. If `$map` is `nil`, a `nil` is returned. If `$map` is not a `map`, an error will be thrown.
- _`parseBool $string`_: parseBool returns the boolean value represented by the string. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error. Alias for [`strconv.ParseBool`](http://golang.org/pkg/strconv/#ParseBool)
- _`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)
- _`sha1 $string`_: Returns the hexadecimal representation of the SHA1 hash of `$string`.
- _`split $string $sep`_: Splits `$string` into a slice of substrings delimited by `$sep`. Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split)
- _`splitN $string $sep $count`_: Splits `$string` into a slice of substrings delimited by `$sep`, with number of substrings returned determined by `$count`. Alias for [`strings.SplitN`](https://golang.org/pkg/strings/#SplitN)
- _`sortStringsAsc $strings`_: Returns a slice of strings `$strings` sorted in ascending order.
- _`sortStringsDesc $strings`_: Returns a slice of strings `$strings` sorted in descending (reverse) order.
- _`sortObjectsByKeysAsc $objects $fieldPath`_: Returns the array `$objects`, sorted in ascending order based on the values of a field path expression `$fieldPath`.
- _`sortObjectsByKeysDesc $objects $fieldPath`_: Returns the array `$objects`, sorted in descending (reverse) order based on the values of a field path expression `$fieldPath`.
- _`trimPrefix $prefix $string`_: If `$prefix` is a prefix of `$string`, return `$string` with `$prefix` trimmed from the beginning. Otherwise, return `$string` unchanged.
- _`trimSuffix $suffix $string`_: If `$suffix` is a suffix of `$string`, return `$string` with `$suffix` trimmed from the end. Otherwise, return `$string` unchanged.
- _`toLower $string`_: Replace capital letters in `$string` to lowercase.
- _`toUpper $string`_: Replace lowercase letters in `$string` to uppercase.
- _`when $condition $trueValue $falseValue`_: Returns the `$trueValue` when the `$condition` is `true` and the `$falseValue` otherwise
- _`where $items $fieldPath $value`_: Filters an array or slice 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. Returns an array of items having that value.
- _`whereNot $items $fieldPath $value`_: Filters an array or slice 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. Returns an array of items **not** having that value.
Expand All @@ -426,6 +416,30 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
- _`whereLabelDoesNotExist $containers $label`_: Filters a slice of containers based on the non-existence of the label `$label`.
- _`whereLabelValueMatches $containers $label $pattern`_: Filters a slice of containers based on the existence of the label `$label` with values matching the regular expression `$pattern`.

Sprig functions that have the same name as docker-gen function (but different behaviour) are made available with the `sprig` prefix:

- _`sprigCoalesce ...`_: Alias for Sprig's [`coalesce`](https://masterminds.github.io/sprig/defaults.html).
- _`sprigContains $string $string`_: Alias for Sprig's [`contains`](https://masterminds.github.io/sprig/strings.html).
- _`sprigDir $path`_: Alias for Sprig's [`dir`](https://masterminds.github.io/sprig/paths.html).
- _`sprigReplace $old $new $string`_: Alias for Sprig's [`replace`](https://masterminds.github.io/sprig/strings.html).
- _`sprigSplit $sep $string`_: Alias for Sprig's [`split`](https://masterminds.github.io/sprig/string_slice.html).
- _`sprigSplitn $sep $count $string"`_: Alias for Sprig's [`splitn`](https://masterminds.github.io/sprig/string_slice.html).

Some functions are aliases for Go's [`strings`](https://pkg.go.dev/strings) package functions:

- _`parseBool $string`_: Alias for [`strconv.ParseBool`](http://golang.org/pkg/strconv/#ParseBool). Returns the boolean value represented by `$string`. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error.
- _`replace $string $old $new $count`_: Alias for [`strings.Replace`](http://golang.org/pkg/strings/#Replace). Replaces up to `$count` occurences of `$old` with `$new` in `$string`.
- _`split $string $sep`_: Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split). Splits `$string` into a slice of substrings delimited by `$sep`.
- _`splitN $string $sep $count`_: Alias for [`strings.SplitN`](https://golang.org/pkg/strings/#SplitN). Splits `$string` into a slice of substrings delimited by `$sep`, with number of substrings returned determined by `$count`.
- _`toLower $string`_: Alias for [`strings.ToLower`](https://pkg.go.dev/strings#ToLower). Replace capital letters in `$string` to lowercase.
- _`toUpper $string`_: Alias for [`strings.ToUpper`](https://pkg.go.dev/strings#ToUpper). Replace lowercase letters in `$string` to uppercase.

Those have been aliased to Sprig functions with the same behaviour as the original docker-gen function:

- _`json $value`_: Alias for Sprig's [`mustToJson`](https://masterminds.github.io/sprig/defaults.html)
- _`parseJson $string`_: Alias for Sprig's [`mustFromJson`](https://masterminds.github.io/sprig/defaults.html).
- _`sha1 $string`_: Alias for Sprig's [`sha1sum`](https://masterminds.github.io/sprig/crypto.html).

---

### Examples
Expand Down
47 changes: 0 additions & 47 deletions internal/template/functions.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package template

import (
"bytes"
"crypto/sha1"
"encoding/json"
"fmt"
"io"
"log"
"os"
"reflect"
Expand Down Expand Up @@ -81,29 +77,6 @@ func contains(input interface{}, key interface{}) bool {
return false
}

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 unmarshalJson(input string) (interface{}, error) {
var v interface{}
if err := json.Unmarshal([]byte(input), &v); err != nil {
return nil, err
}
return v, nil
}

// arrayClosest find the longest matching substring in values
// that matches input
func arrayClosest(values []string, input string) string {
Expand Down Expand Up @@ -140,26 +113,6 @@ func coalesce(input ...interface{}) interface{} {
return nil
}

// trimPrefix returns a string without the prefix, if present
func trimPrefix(prefix, s string) string {
return strings.TrimPrefix(s, prefix)
}

// trimSuffix returns a string without the suffix, if present
func trimSuffix(suffix, s string) string {
return strings.TrimSuffix(s, suffix)
}

// toLower return the string in lower case
func toLower(s string) string {
return strings.ToLower(s)
}

// toUpper return the string in upper case
func toUpper(s string) string {
return strings.ToUpper(s)
}

// when returns the trueValue when the condition is true and the falseValue otherwise
func when(condition bool, trueValue, falseValue interface{}) interface{} {
if condition {
Expand Down
82 changes: 0 additions & 82 deletions internal/template/functions_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package template

import (
"bytes"
"encoding/json"
"os"
"path"
"reflect"
"testing"

"github.com/nginx-proxy/docker-gen/internal/context"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -130,85 +127,6 @@ func TestSplitN(t *testing.T) {
tests.run(t)
}

func TestTrimPrefix(t *testing.T) {
const prefix = "tcp://"
const str = "tcp://127.0.0.1:2375"
const trimmed = "127.0.0.1:2375"
got := trimPrefix(prefix, str)
if got != trimmed {
t.Fatalf("expected trimPrefix(%s,%s) to be %s, got %s", prefix, str, trimmed, got)
}
}

func TestTrimSuffix(t *testing.T) {
const suffix = ".local"
const str = "myhost.local"
const trimmed = "myhost"
got := trimSuffix(suffix, str)
if got != trimmed {
t.Fatalf("expected trimSuffix(%s,%s) to be %s, got %s", suffix, str, trimmed, got)
}
}

func TestToLower(t *testing.T) {
const str = ".RaNd0m StrinG_"
const lowered = ".rand0m string_"
assert.Equal(t, lowered, toLower(str), "Unexpected value from toLower()")
}

func TestToUpper(t *testing.T) {
const str = ".RaNd0m StrinG_"
const uppered = ".RAND0M STRING_"
assert.Equal(t, uppered, toUpper(str), "Unexpected value from toUpper()")
}

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

func TestJson(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
},
ID: "2",
},
{
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 []*context.RuntimeContainer
if err := dec.Decode(&decoded); err != nil {
t.Fatal(err)
}
if len(decoded) != len(containers) {
t.Fatalf("Incorrect unmarshaled container count. Expected %d, got %d.", len(containers), len(decoded))
}
}

func TestParseJson(t *testing.T) {
tests := templateTestList{
{`{{parseJson .}}`, `null`, `<no value>`},
Expand Down
28 changes: 20 additions & 8 deletions internal/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ func newTemplate(name string) *template.Template {
}
return buf.String(), nil
}
tmpl.Funcs(sprig.TxtFuncMap()).Funcs(template.FuncMap{

sprigFuncMap := sprig.TxtFuncMap()

tmpl.Funcs(sprigFuncMap).Funcs(template.FuncMap{
"closest": arrayClosest,
"coalesce": coalesce,
"comment": comment,
Expand All @@ -71,29 +74,24 @@ func newTemplate(name string) *template.Template {
"groupByMulti": groupByMulti,
"groupByLabel": groupByLabel,
"groupByLabelWithDefault": groupByLabelWithDefault,
"json": marshalJson,
"include": include,
"intersect": intersect,
"keys": keys,
"replace": strings.Replace,
"parseBool": strconv.ParseBool,
"parseJson": unmarshalJson,
"fromYaml": fromYaml,
"toYaml": toYaml,
"mustFromYaml": mustFromYaml,
"mustToYaml": mustToYaml,
"queryEscape": url.QueryEscape,
"sha1": hashSha1,
"split": strings.Split,
"splitN": strings.SplitN,
"sortStringsAsc": sortStringsAsc,
"sortStringsDesc": sortStringsDesc,
"sortObjectsByKeysAsc": sortObjectsByKeysAsc,
"sortObjectsByKeysDesc": sortObjectsByKeysDesc,
"trimPrefix": trimPrefix,
"trimSuffix": trimSuffix,
"toLower": toLower,
"toUpper": toUpper,
"toLower": strings.ToLower,
"toUpper": strings.ToUpper,
"when": when,
"where": where,
"whereNot": whereNot,
Expand All @@ -104,7 +102,21 @@ func newTemplate(name string) *template.Template {
"whereLabelExists": whereLabelExists,
"whereLabelDoesNotExist": whereLabelDoesNotExist,
"whereLabelValueMatches": whereLabelValueMatches,

// legacy docker-gen template function aliased to their Sprig clone
"json": sprigFuncMap["mustToJson"],
"parseJson": sprigFuncMap["mustFromJson"],
"sha1": sprigFuncMap["sha1sum"],

// aliases to sprig template functions masked by docker-gen functions with the same name
"sprigCoalesce": sprigFuncMap["coalesce"],
"sprigContains": sprigFuncMap["contains"],
"sprigDir": sprigFuncMap["dir"],
"sprigReplace": sprigFuncMap["replace"],
"sprigSplit": sprigFuncMap["split"],
"sprigSplitn": sprigFuncMap["splitn"],
})

return tmpl
}

Expand Down