Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): extVars #178

Merged
merged 1 commit into from
Jan 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion cmd/tk/jsonnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package main

import (
"encoding/json"
"fmt"
"log"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/grafana/tanka/pkg/tanka"
)
Expand All @@ -19,8 +22,13 @@ func evalCmd() *cobra.Command {
},
}

getExtCode := extCodeParser(cmd.Flags())

cmd.Run = func(cmd *cobra.Command, args []string) {
raw, _, err := tanka.Eval(args[0], nil)
raw, err := tanka.Eval(args[0],
tanka.WithExtCode(getExtCode()),
)

if err != nil {
log.Fatalln(err, nil)
}
Expand All @@ -35,3 +43,30 @@ func evalCmd() *cobra.Command {

return cmd
}

func extCodeParser(fs *pflag.FlagSet) func() map[string]string {
// need to use StringArray instead of StringSlice, because pflag attempts to
// parse StringSlice using the csv parser, which breaks when passing objects
values := fs.StringArrayP("extCode", "e", nil, "Inject any Jsonnet from the outside (Format: key=<code>)")
strs := fs.StringArray("extVar", nil, "Inject a string from the outside (Format: key=value)")

return func() map[string]string {
m := make(map[string]string)
for _, s := range *values {
split := strings.SplitN(s, "=", 2)
if len(split) != 2 {
log.Fatalln("extCode argument has wrong format:", s+".", "Expected 'key=<code>'")
}
m[split[0]] = split[1]
}

for _, s := range *strs {
split := strings.SplitN(s, "=", 2)
if len(split) != 2 {
log.Fatalln("extVar argument has wrong format:", s+".", "Expected 'key=value'")
}
m[split[0]] = fmt.Sprintf(`"%s"`, split[1])
}
return m
}
}
8 changes: 8 additions & 0 deletions cmd/tk/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ func applyCmd() *cobra.Command {
vars := workflowFlags(cmd.Flags())
force := cmd.Flags().Bool("force", false, "force applying (kubectl apply --force)")
autoApprove := cmd.Flags().Bool("dangerous-auto-approve", false, "skip interactive approval. Only for automation!")
getExtCode := extCodeParser(cmd.Flags())

cmd.Run = func(cmd *cobra.Command, args []string) {
err := tanka.Apply(args[0],
tanka.WithTargets(stringsToRegexps(vars.targets)...),
tanka.WithExtCode(getExtCode()),
tanka.WithApplyForce(*force),
tanka.WithApplyAutoApprove(*autoApprove),
)
Expand Down Expand Up @@ -79,9 +82,12 @@ func diffCmd() *cobra.Command {
summarize = cmd.Flags().BoolP("summarize", "s", false, "quick summary of the differences, hides file contents")
)

getExtCode := extCodeParser(cmd.Flags())

cmd.Run = func(cmd *cobra.Command, args []string) {
changes, err := tanka.Diff(args[0],
tanka.WithTargets(stringsToRegexps(vars.targets)...),
tanka.WithExtCode(getExtCode()),
tanka.WithDiffStrategy(*diffStrategy),
tanka.WithDiffSummarize(*summarize),
)
Expand Down Expand Up @@ -118,13 +124,15 @@ func showCmd() *cobra.Command {
}
vars := workflowFlags(cmd.Flags())
allowRedirect := cmd.Flags().Bool("dangerous-allow-redirect", false, "allow redirecting output to a file or a pipe.")
getExtCode := extCodeParser(cmd.Flags())
cmd.Run = func(cmd *cobra.Command, args []string) {
if !interactive && !*allowRedirect {
fmt.Fprintln(os.Stderr, "Redirection of the output of tk show is discouraged and disabled by default. Run tk show --dangerous-allow-redirect to enable.")
return
}

pretty, err := tanka.Show(args[0],
tanka.WithExtCode(getExtCode()),
tanka.WithTargets(stringsToRegexps(vars.targets)...),
)
if err != nil {
Expand Down
30 changes: 24 additions & 6 deletions pkg/tanka/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (p *ParseResult) newKube() (*kubernetes.Kubernetes, error) {
// parse loads the `spec.json`, evaluates the jsonnet and returns both, the
// kubernetes object and the reconciled manifests
func parse(baseDir string, opts *options) (*ParseResult, error) {
raw, env, err := Eval(baseDir, opts)
raw, env, err := eval(baseDir, opts)
if err != nil {
return nil, err
}
Expand All @@ -51,8 +51,19 @@ func parse(baseDir string, opts *options) (*ParseResult, error) {
}, nil
}

// Eval returns the raw evaluated Jsonnet and the parsed env used for evaluation
func Eval(baseDir string, opts *options) (raw map[string]interface{}, env *v1alpha1.Config, err error) {
// Eval returns the raw evaluated Jsonnet output (without any transformations)
func Eval(baseDir string, mods ...Modifier) (raw map[string]interface{}, err error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is even better than before, where an exported function was using an unexported type :)

opts := parseModifiers(mods)

r, _, err := eval(baseDir, opts)
if err != nil {
return nil, err
}
return r, nil
}

// eval returns the raw evaluated Jsonnet and the parsed env used for evaluation
func eval(baseDir string, opts *options) (raw map[string]interface{}, env *v1alpha1.Config, err error) {
if opts == nil {
opts = &options{}
}
Expand All @@ -62,7 +73,7 @@ func Eval(baseDir string, opts *options) (raw map[string]interface{}, env *v1alp
return nil, nil, err
}

raw, err = evalJsonnet(baseDir, env)
raw, err = evalJsonnet(baseDir, env, opts.extCode)
if err != nil {
return nil, nil, errors.Wrap(err, "evaluating jsonnet")
}
Expand Down Expand Up @@ -96,7 +107,7 @@ func parseEnv(baseDir string, opts *options) (*v1alpha1.Config, error) {

// evalJsonnet evaluates the jsonnet environment at the given directory starting with
// `main.jsonnet`
func evalJsonnet(path string, env *v1alpha1.Config) (map[string]interface{}, error) {
func evalJsonnet(path string, env *v1alpha1.Config, extCode map[string]string) (map[string]interface{}, error) {
workdir, err := filepath.Abs(path)
if err != nil {
return nil, err
Expand All @@ -111,9 +122,16 @@ func evalJsonnet(path string, env *v1alpha1.Config) (map[string]interface{}, err
return nil, errors.Wrap(err, "marshalling environment config")
}

ext := []jsonnet.Modifier{
jsonnet.WithExtCode(spec.APIGroup+"/environment", string(jsonEnv)),
}
for k, v := range extCode {
ext = append(ext, jsonnet.WithExtCode(k, v))
}

raw, err := jsonnet.EvaluateFile(
filepath.Join(baseDir, "main.jsonnet"),
jsonnet.WithExtCode(spec.APIGroup+"/environment", string(jsonEnv)),
ext...,
)
if err != nil {
return nil, err
Expand Down
10 changes: 10 additions & 0 deletions pkg/tanka/tanka.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type options struct {
// io.Writer to write warnings and notices to
wWarn io.Writer

// `std.extVar`
extCode map[string]string

// target regular expressions to limit the working set
targets []*regexp.Regexp

Expand All @@ -46,6 +49,13 @@ func WithWarnWriter(w io.Writer) Modifier {
}
}

// WithExtCode allows to pass external variables (jsonnet code) to the VM
func WithExtCode(code map[string]string) Modifier {
return func(opts *options) {
opts.extCode = code
}
}

// WithTargets allows to submit regular expressions to limit the working set of
// objects (https://tanka.dev/targets/).
func WithTargets(t ...*regexp.Regexp) Modifier {
Expand Down