Skip to content

Commit

Permalink
Added --ignore-unset CLI flag
Browse files Browse the repository at this point in the history
  • Loading branch information
shyiko committed Sep 29, 2018
1 parent 0186698 commit fcdef08
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 16 deletions.
43 changes: 33 additions & 10 deletions engine/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,27 @@ import (
)

type ShellTemplate struct {
content []byte
content []byte
ignoreUnset bool
}

func NewShellTemplate(template []byte) (Template, error) {
return ShellTemplate{template}, nil
type ShellTemplateOption = func(*ShellTemplate) error

func ShellTemplateIgnoreUnset() ShellTemplateOption {
return func(t *ShellTemplate) error {
t.ignoreUnset = true
return nil
}
}

func NewShellTemplate(template []byte, options ...ShellTemplateOption) (Template, error) {
tpl := ShellTemplate{template, false}
for _, option := range options {
if err := option(&tpl); err != nil {
return nil, err
}
}
return tpl, nil
}

func (t ShellTemplate) Render(data map[string]interface{}) (res []byte, err error) {
Expand All @@ -31,14 +47,14 @@ func (t ShellTemplate) Render(data map[string]interface{}) (res []byte, err erro
return nil, err
}
}
r, err := envsubst(string(t.content), data)
r, err := envsubst(string(t.content), data, t.ignoreUnset)
if err != nil {
return nil, err
}
return []byte(r), nil
}

func envsubst(value string, env map[string]interface{}) (res string, err error) {
func envsubst(value string, env map[string]interface{}, ignoreUnset bool) (res string, err error) {
defer func() {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
Expand All @@ -47,23 +63,26 @@ func envsubst(value string, env map[string]interface{}) (res string, err error)
err = r.(error)
}
}()
res = expandWithLineColumnInfo(value, func(key string, line int, col int) string {
res = expandWithLineColumnInfo(value, func(key string, line int, col int) (string, bool) {
if key == "$" || key == "" {
return "$"
return "$", true
}
value, ok := env[key]
if !ok || value == nil {
if ignoreUnset {
return "", false
}
panic(fmt.Errorf("%d:%d: \"%s\" isn't set", line, col, key))
}
if !yamlext.IsBasicType(value) {
panic(fmt.Errorf("%d:%d: \"%s\" must be either a string, number or a boolean", line, col, key))
}
return fmt.Sprintf("%v", value)
return fmt.Sprintf("%v", value), true
})
return
}

func expandWithLineColumnInfo(s string, mapping func(string, int, int) string) string {
func expandWithLineColumnInfo(s string, mapping func(string, int, int) (string, bool)) string {
buf := make([]byte, 0, 2*len(s))
i, l, n := 0, 0, 0
for j := 0; j < len(s); j++ {
Expand All @@ -73,7 +92,11 @@ func expandWithLineColumnInfo(s string, mapping func(string, int, int) string) s
} else if s[j] == '$' && j+1 < len(s) {
buf = append(buf, s[i:j]...)
name, w := getShellName(s[j+1:])
buf = append(buf, mapping(name, l+1, j-n+1)...)
if v, ok := mapping(name, l+1, j-n+1); ok {
buf = append(buf, v...)
} else {
buf = append(buf, s[j:j+1+w]...)
}
j += w
i = j + 1
}
Expand Down
38 changes: 36 additions & 2 deletions engine/shell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func init() {

func TestShellTemplateRender(t *testing.T) {
actual, err := ShellTemplate{
[]byte(`# kubetpl:syntax:$
content: []byte(`# kubetpl:syntax:$
apiVersion: apps/v1beta1
kind: Deployment
metadata:
Expand All @@ -22,6 +22,7 @@ metadata:
spec:
replicas: $REPLICAS
`),
ignoreUnset: false,
}.Render(map[string]interface{}{
"NAME": "app",
"NOT_USED": "value",
Expand All @@ -48,11 +49,12 @@ spec:

func TestShellTemplateRenderIncomplete(t *testing.T) {
_, err := ShellTemplate{
[]byte(`apiVersion: apps/v1beta1
content: []byte(`apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: $NAME-deployment
`),
ignoreUnset: false,
}.Render(map[string]interface{}{
"NOT_USED": "value",
})
Expand All @@ -64,3 +66,35 @@ metadata:
t.Fatalf("actual: \n%s != expected: \n%s", err.Error(), expected)
}
}

func TestShellTemplateRenderUnresolved(t *testing.T) {
actual, err := ShellTemplate{
content: []byte(`apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: $NAME-deployment
annotations:
replicas-as-string: "${REPLICAS}" # $ $$ test
spec:
replicas: $REPLICAS
`),
ignoreUnset: true,
}.Render(map[string]interface{}{
"NAME": "app",
})
if err != nil {
t.Fatal(err)
}
expected := `apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: app-deployment
annotations:
replicas-as-string: "${REPLICAS}" # $ $ test
spec:
replicas: $REPLICAS
`
if string(actual) != expected {
t.Fatalf("actual: \n%s != expected: \n%s", string(actual), expected)
}
}
15 changes: 11 additions & 4 deletions kubetpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func main() {
}
var syntax, chroot string
var configFiles, configKeyValuePairs, freezeRefs, freezeList []string
var allowFsAccess, freeze bool
var allowFsAccess, ignoreUnset, freeze bool
rootCmd := &cobra.Command{
Use: "kubetpl",
Long: "Kubernetes templates made easy (https://github.com/shyiko/kubetpl).",
Expand Down Expand Up @@ -133,6 +133,7 @@ func main() {
freeze: freeze,
freezeRefs: freezeRefs,
freezeList: normalizedFreezeList,
ignoreUnset: ignoreUnset,
})
if err != nil {
log.Fatal(err)
Expand All @@ -153,6 +154,7 @@ func main() {
" kubetpl render template.yml -i staging.env -s KEY=VALUE",
}
renderCmd.Flags().BoolVarP(&freeze, "freeze", "z", false, "Freeze ConfigMap/Secret|s")
renderCmd.Flags().BoolVar(&ignoreUnset, "ignore-unset", false, "Keep $VAR/${VAR} if not set (e.g. \"echo 'kind: $A$B' | kubetpl r - -s A=X --syntax=$ --ignore-unset\" prints \"kind: X$B\")")
renderCmd.Flags().StringArrayVar(&freezeRefs, "freeze-ref", nil,
"External ConfigMap/Secret|s that should not be included in the output and yet references to which need to be '--freeze'd")
renderCmd.Flags().StringSliceVar(&freezeList, "freeze-list", nil,
Expand Down Expand Up @@ -265,6 +267,7 @@ type renderOpts struct {
freeze bool
freezeRefs []string
freezeList []string
ignoreUnset bool
}

func render(templateFiles []string, data map[string]interface{}, opts renderOpts) ([]byte, error) {
Expand Down Expand Up @@ -349,7 +352,7 @@ func renderTemplates(templateFiles []string, config map[string]interface{}, opts
}

func renderTemplate(templateFile string, config map[string]interface{}, opts renderOpts) ([]document, error) {
t, directives, err := newTemplate(templateFile, opts.format)
t, directives, err := newTemplate(templateFile, opts.format, opts.ignoreUnset)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -425,7 +428,7 @@ func dirnameAbs(path string) (string, error) {
return filepath.Abs(filepath.Dir(path))
}

func newTemplate(file string, flavor string) (engine.Template, []directive, error) {
func newTemplate(file string, flavor string, ignoreUnset bool) (engine.Template, []directive, error) {
content, err := readFile(file)
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -455,7 +458,11 @@ func newTemplate(file string, flavor string) (engine.Template, []directive, erro
var t engine.Template
switch flavor {
case "$":
t, err = engine.NewShellTemplate(content)
var opts []engine.ShellTemplateOption
if ignoreUnset {
opts = append(opts, engine.ShellTemplateIgnoreUnset())
}
t, err = engine.NewShellTemplate(content, opts...)
case "go-template":
t, err = engine.NewGoTemplate(content, file)
case "template-kind":
Expand Down

0 comments on commit fcdef08

Please sign in to comment.