feat: Ability to call arbitrary command from a template (#282)
Resolves #244
This commit is contained in:
parent
6be53b1bcc
commit
8a90e5320c
21
README.md
21
README.md
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ package tmpl
|
|||
import (
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
@ -21,6 +25,80 @@ 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...)
|
||||
|
||||
writeErrs := make(chan error)
|
||||
cmdErrs := make(chan error)
|
||||
cmdOuts := make(chan []byte)
|
||||
|
||||
if len(input) > 0 {
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
go func(input string, stdin io.WriteCloser) {
|
||||
defer stdin.Close()
|
||||
defer close(writeErrs)
|
||||
|
||||
size := len(input)
|
||||
|
||||
var n int
|
||||
var err error
|
||||
i := 0
|
||||
for {
|
||||
n, err = io.WriteString(stdin, input[i:])
|
||||
if err != nil {
|
||||
writeErrs <- fmt.Errorf("failed while writing %d bytes to stdin of \"%s\": %v", len(input), command, err)
|
||||
break
|
||||
}
|
||||
i += n
|
||||
if n == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
}(input, stdin)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(cmdOuts)
|
||||
defer close(cmdErrs)
|
||||
|
||||
bytes, err := cmd.Output()
|
||||
if err != nil {
|
||||
cmdErrs <- fmt.Errorf("exec cmd=%s args=[%s] failed: %v", command, strings.Join(strArgs, ", "), err)
|
||||
} else {
|
||||
cmdOuts <- bytes
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case bytes := <-cmdOuts:
|
||||
return string(bytes), nil
|
||||
case err := <-cmdErrs:
|
||||
return "", err
|
||||
case err := <-writeErrs:
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) ReadFile(filename string) (string, error) {
|
||||
path := filepath.Join(c.basePath, filename)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue