Skip to content

Commit

Permalink
feat: Ability to call arbitrary command from a template
Browse files Browse the repository at this point in the history
Resolves roboll#244
  • Loading branch information
mumoshu committed Sep 3, 2018
1 parent 54f1567 commit 3e90b13
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 0 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,27 @@ Just run `helmfile sync` inside `myteam/`, and you are done.

All the files are sorted alphabetically per group = array item inside `helmfiles:`, so that you have granular control over ordering, too.

## Importing values from any source

The `exec` template function that is available in `values.yaml.gotmpl` is useful for importing values from any source
that is accessible by running a command:

An usual usage of `exec` would look like this:

```
mysetting: |
{{ exec "./mycmd" (list "arg1" "arg2" "--flag1") | indent 2 }}
```

Or even with a pipeline:

```
mysetting: |
{{ yourinput | exec "./mycmd-consume-stdin" (list "arg1" "arg2") | indent 2 }}
```

The possibility is endless. Try importing values from your golang app, bash script, jsonnet, or anything!

## Using env files

helmfile itself doesn't have an ability to load env files. But you can write some bash script to achieve the goal:
Expand Down
56 changes: 56 additions & 0 deletions tmpl/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package tmpl
import (
"fmt"
"gopkg.in/yaml.v2"
"io"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"text/template"
)
Expand All @@ -13,6 +16,7 @@ type Values = map[string]interface{}

func (c *Context) createFuncMap() template.FuncMap {
return template.FuncMap{
"exec": c.Exec,
"readFile": c.ReadFile,
"toYaml": ToYaml,
"fromYaml": FromYaml,
Expand All @@ -21,6 +25,58 @@ func (c *Context) createFuncMap() template.FuncMap {
}
}

func (c *Context) Exec(command string, args []interface{}, inputs ...string) (string, error) {
var input string
if len(inputs) > 0 {
input = inputs[0]
}

strArgs := make([]string, len(args))
for i, a := range args {
switch a.(type) {
case string:
strArgs[i] = a.(string)
default:
return "", fmt.Errorf("unexpected type of arg \"%s\" in args %v at index %d", reflect.TypeOf(a), args, i)
}
}

cmd := exec.Command(command, strArgs...)

if len(input) > 0 {
stdin, err := cmd.StdinPipe()
if err != nil {
return "", err
}
go func(input string, stdin io.WriteCloser) {
defer stdin.Close()

size := len(input)

var n int
var err error
i := 0
for {
n, err = io.WriteString(stdin, input[i:])
if err != nil {
panic(err)
break
}
i += n
if n == size {
break
}
}
}(input, stdin)
}

bytes, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("exec cmd=%s args=[%s] failed: %v", command, strings.Join(strArgs, ", "), err)
}
return string(bytes), nil
}

func (c *Context) ReadFile(filename string) (string, error) {
path := filepath.Join(c.basePath, filename)

Expand Down

0 comments on commit 3e90b13

Please sign in to comment.