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.
|
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
|
## 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:
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
@ -13,6 +16,7 @@ type Values = map[string]interface{}
|
||||||
|
|
||||||
func (c *Context) createFuncMap() template.FuncMap {
|
func (c *Context) createFuncMap() template.FuncMap {
|
||||||
return template.FuncMap{
|
return template.FuncMap{
|
||||||
|
"exec": c.Exec,
|
||||||
"readFile": c.ReadFile,
|
"readFile": c.ReadFile,
|
||||||
"toYaml": ToYaml,
|
"toYaml": ToYaml,
|
||||||
"fromYaml": FromYaml,
|
"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) {
|
func (c *Context) ReadFile(filename string) (string, error) {
|
||||||
path := filepath.Join(c.basePath, filename)
|
path := filepath.Join(c.basePath, filename)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue