Add sort function
Could a sort function be added? I imagine it taking an array of strings and returning a sorted array of strings.
My rendered templates have different contents from one run to the next because containers irrelevant to the template are being created and destroyed. It's essentially rendering the same file, but in different order. By specifying the order of my generated configuration, docker-gen will avoid making unnecessary changes to the rendered file.
My apologies if this is already possible.
Example of a problematic template (both ranges are unstable):
{{ range $host := groupByKeys $ "Labels.PROXY_HOST" }}
{{ trim $host }}
{{ end }}
{{ range $host := keys (groupByMulti $ "Labels.PROXY_REDIRECT" ",") }}
{{ trim $host }}
{{ end }}
Potential use of sort, to render a stable file:
{{ range $host := sort (groupByKeys $ "Labels.PROXY_HOST") }}
{{ trim $host }}
{{ end }}
{{ range $host := sort (keys (groupByMulti $ "Labels.PROXY_REDIRECT" ",")) }}
{{ trim $host }}
{{ end }}
I'm generating an index.html file in my nginx reverse proxy and I want a sorted output there, too. For this I've patched docker-gen the following way:
Index: src/github.com/jwilder/docker-gen/template.go
--- src/github.com/jwilder/docker-gen/template.go.orig 2017-11-25 19:53:49.000000000 +0100
+++ src/github.com/jwilder/docker-gen/template.go 2017-11-26 00:00:54.533302000 +0100
@@ -16,6 +16,7 @@
"regexp"
"strconv"
"strings"
+ "sort"
"syscall"
"text/template"
)
@@ -402,6 +403,30 @@
return strings.TrimSpace(s)
}
+// sortStrings returns a sorted array of strings
+func sortStrings(values []string) []string {
+ sort.Strings(values)
+ return values
+}
+
+// sortObjects returns a sorted array of objects (sorted by object key field)
+func sortObjects(objs interface{}, key string) (interface{}, error) {
+ objsVal, err := getArrayValues("sortObj", objs)
+ if err != nil {
+ return nil, err
+ }
+ data := make([]interface{}, objsVal.Len())
+ for i := 0; i < objsVal.Len(); i++ {
+ data[i] = objsVal.Index(i).Interface()
+ }
+ sort.Slice(data, func(i, j int) bool {
+ a := reflect.ValueOf(deepGet(data[i], key)).Interface().(string)
+ b := reflect.ValueOf(deepGet(data[j], key)).Interface().(string)
+ return a < b
+ })
+ return data, nil
+}
+
// when returns the trueValue when the condition is true and the falseValue otherwise
func when(condition bool, trueValue, falseValue interface{}) interface{} {
if condition {
@@ -440,6 +465,8 @@
"trimPrefix": trimPrefix,
"trimSuffix": trimSuffix,
"trim": trim,
+ "sortStrings": sortStrings,
+ "sortObjects": sortObjects,
"when": when,
"where": where,
"whereExist": whereExist,
I'm primarily hacking in JavaScript today and I'm not a Go expert, so chances are high that the implementation can be simplified, of course. But it at least works for my use-case:
[...]
<div class="services">
{{ range $paramUrlHost, $containers := groupByMulti $ "Env.PROXY_URL_HOST" "," }}
{{ $containers := sortObjects $containers "Env.PROXY_SRV_NAME" }}
{{ range $index, $container := $containers }}
{{ $paramUrlScheme := $container.Env.PROXY_URL_SCHEME }}
{{ $paramUrlPath := $container.Env.PROXY_URL_PATH }}
{{ $paramSrvName := $container.Env.PROXY_SRV_NAME }}
{{ $paramSrvDesc := $container.Env.PROXY_SRV_DESC }}
<a class="service" href="{{ $paramUrlScheme }}://{{ $paramUrlHost }}{{ $paramUrlPath }}">
<div class="name">{{ $paramSrvName }}</div>
<div class="desc">{{ $paramSrvDesc }}</div>
</a>
{{ end }}
{{ end }}
</div>
[...]
Feel free to improve and take over into the official docker-gen source code base...
This was solved indirectly when we added the Sprig template functions: https://masterminds.github.io/sprig/string_slice.html