From 78ae61b063adf583e2c85f95df0d1971e69077d2 Mon Sep 17 00:00:00 2001 From: Duologic Date: Thu, 26 Nov 2020 22:08:52 +0100 Subject: [PATCH] refactor: reduce complexity in parse.go --- cmd/tk/main.go | 2 +- pkg/tanka/errors.go | 4 + pkg/tanka/parse.go | 177 ++++++++++++++++++++-------------------- pkg/tanka/parse_test.go | 2 +- pkg/tanka/workflow.go | 2 +- 5 files changed, 96 insertions(+), 91 deletions(-) diff --git a/cmd/tk/main.go b/cmd/tk/main.go index a15afbfca..87b927906 100644 --- a/cmd/tk/main.go +++ b/cmd/tk/main.go @@ -80,7 +80,7 @@ func setupConfiguration(baseDir string) *v1alpha1.Environment { } // no spec.json is found, try parsing main.jsonnet case spec.ErrNoSpec: - config, err := tanka.EvalEnvs(baseDir, jsonnet.Opts{}) + _, config, err := tanka.ParseEnv(baseDir, jsonnet.Opts{}, tanka.EnvsOnlyEvaluator) if err != nil { switch err.(type) { case tanka.ErrNoEnv: diff --git a/pkg/tanka/errors.go b/pkg/tanka/errors.go index 546285b09..26e3176d7 100644 --- a/pkg/tanka/errors.go +++ b/pkg/tanka/errors.go @@ -1,10 +1,14 @@ package tanka import ( + "errors" "fmt" "strings" ) +// ErrInvalidEvaluator +var ErrInvalidEvaluator = errors.New("invalid evaluator type") + // ErrNoEnv means that the given jsonnet has no Environment object // This must not be fatal, some operations work without type ErrNoEnv struct { diff --git a/pkg/tanka/parse.go b/pkg/tanka/parse.go index 2cc87e815..555d4d165 100644 --- a/pkg/tanka/parse.go +++ b/pkg/tanka/parse.go @@ -22,6 +22,10 @@ import ( // provided using ldflags const DEFAULT_DEV_VERSION = "dev" +// +const DefaultEvaluator = "Default" +const EnvsOnlyEvaluator = "EnvsOnly" + // CURRENT_VERSION is the current version of the running Tanka code var CURRENT_VERSION = DEFAULT_DEV_VERSION @@ -65,7 +69,7 @@ func (p *loaded) connect() (*kubernetes.Kubernetes, error) { // load runs all processing stages described at the Processed type func load(path string, opts Opts) (*loaded, error) { - _, env, err := eval(path, opts.JsonnetOpts) + _, env, err := ParseEnv(path, opts.JsonnetOpts, DefaultEvaluator) if err != nil { return nil, err } @@ -93,36 +97,6 @@ func load(path string, opts Opts) (*loaded, error) { }, nil } -// eval evaluates the jsonnet environment at the given file system path -func eval(path string, opts jsonnet.Opts) (interface{}, *v1alpha1.Environment, error) { - return parseEnv( - path, - opts, - func(path string, opts jsonnet.Opts) (string, error) { - entrypoint, err := jpath.Entrypoint(path) - if err != nil { - return "", err - } - - // evaluate Jsonnet - var raw string - if opts.EvalPattern != "" { - evalScript := fmt.Sprintf("(import '%s').%s", entrypoint, opts.EvalPattern) - raw, err = jsonnet.Evaluate(entrypoint, evalScript, opts) - if err != nil { - return "", errors.Wrap(err, "evaluating jsonnet") - } - } else { - raw, err = jsonnet.EvaluateFile(entrypoint, opts) - if err != nil { - return "", errors.Wrap(err, "evaluating jsonnet") - } - } - return raw, nil - }, - ) -} - // parseSpec parses the `spec.json` of the environment and returns a // *kubernetes.Kubernetes from it func parseSpec(path string) (*v1alpha1.Environment, error) { @@ -152,10 +126,80 @@ func parseSpec(path string) (*v1alpha1.Environment, error) { return config, nil } -type evaluateFunc func(path string, opts jsonnet.Opts) (string, error) +// defaultEvaluator evaluates the jsonnet environment at the given file system path +func defaultEvaluator(path string, opts jsonnet.Opts) (string, error) { + entrypoint, err := jpath.Entrypoint(path) + if err != nil { + return "", err + } + + // evaluate Jsonnet + var raw string + if opts.EvalPattern != "" { + evalScript := fmt.Sprintf("(import '%s').%s", entrypoint, opts.EvalPattern) + raw, err = jsonnet.Evaluate(entrypoint, evalScript, opts) + if err != nil { + return "", errors.Wrap(err, "evaluating jsonnet") + } + } else { + raw, err = jsonnet.EvaluateFile(entrypoint, opts) + if err != nil { + return "", errors.Wrap(err, "evaluating jsonnet") + } + } + return raw, nil +} + +// envsOnlyEvaluator finds the Environment object (without its .data object) at +// the given file system path intended for use by the `tk env` command +func envsOnlyEvaluator(path string, opts jsonnet.Opts) (string, error) { + entrypoint, err := jpath.Entrypoint(path) + if err != nil { + return "", err + } + + // Snippet to find all Environment objects and remove the .data object for faster evaluation + noData := ` +local noDataEnv(object) = + if std.isObject(object) + then + if std.objectHas(object, 'apiVersion') + && std.objectHas(object, 'kind') + then + if object.kind == 'Environment' + then object { data:: {} } + else {} + else + std.mapWithKey( + function(key, obj) + noDataEnv(obj), + object + ) + else if std.isArray(object) + then + std.map( + function(obj) + noDataEnv(obj), + object + ) + else {}; + +noDataEnv(import '%s') +` + + // evaluate Jsonnet with noData snippet + var raw string + evalScript := fmt.Sprintf(noData, entrypoint) + raw, err = jsonnet.Evaluate(entrypoint, evalScript, opts) + if err != nil { + return "", errors.Wrap(err, "evaluating jsonnet") + } + return raw, nil +} -// parseEnv finds the Environment object at the given file system path -func parseEnv(path string, opts jsonnet.Opts, evalFn evaluateFunc) (interface{}, *v1alpha1.Environment, error) { +// ParseEnv evaluates the jsonnet environment at the given file system path and +// optionally also returns and Environment object +func ParseEnv(path string, opts jsonnet.Opts, evaluator string) (interface{}, *v1alpha1.Environment, error) { specEnv, err := parseSpec(path) if err != nil { switch err.(type) { @@ -174,6 +218,16 @@ func parseEnv(path string, opts jsonnet.Opts, evalFn evaluateFunc) (interface{}, opts.ExtCode.Set(spec.APIGroup+"/environment", string(jsonEnv)) } + var evalFn func(path string, opts jsonnet.Opts) (string, error) + switch evaluator { + case EnvsOnlyEvaluator: + evalFn = envsOnlyEvaluator + case DefaultEvaluator: + evalFn = defaultEvaluator + default: + return nil, nil, ErrInvalidEvaluator + } + raw, err := evalFn(path, opts) if err != nil { return nil, nil, err @@ -251,6 +305,7 @@ func checkVersion(constraint string) error { return nil } +// extraEnvironments filters out any Environment manifests func extractEnvironments(data interface{}) (manifest.List, error) { // Scan for everything that looks like a Kubernetes object extracted, err := process.Extract(data) @@ -271,57 +326,3 @@ func extractEnvironments(data interface{}) (manifest.List, error) { // Extract only object of Kind: Environment return process.Filter(out, process.MustStrExps("Environment/.*")), nil } - -// EvalEnvs finds the Environment object (without its .data object) at the -// given file system path intended for use by the `tk env` command -func EvalEnvs(path string, opts jsonnet.Opts) (*v1alpha1.Environment, error) { - _, env, err := parseEnv( - path, - opts, - func(path string, opts jsonnet.Opts) (string, error) { - entrypoint, err := jpath.Entrypoint(path) - if err != nil { - return "", err - } - - // Snippet to find all Environment objects and remove the .data object for faster evaluation - noData := ` -local noDataEnv(object) = - if std.isObject(object) - then - if std.objectHas(object, 'apiVersion') - && std.objectHas(object, 'kind') - then - if object.kind == 'Environment' - then object { data:: {} } - else {} - else - std.mapWithKey( - function(key, obj) - noDataEnv(obj), - object - ) - else if std.isArray(object) - then - std.map( - function(obj) - noDataEnv(obj), - object - ) - else {}; - -noDataEnv(import '%s') -` - - // evaluate Jsonnet with noData snippet - var raw string - evalScript := fmt.Sprintf(noData, entrypoint) - raw, err = jsonnet.Evaluate(entrypoint, evalScript, opts) - if err != nil { - return "", errors.Wrap(err, "evaluating jsonnet") - } - return raw, nil - }, - ) - return env, err -} diff --git a/pkg/tanka/parse_test.go b/pkg/tanka/parse_test.go index ad3d5ac5b..ef532ab40 100644 --- a/pkg/tanka/parse_test.go +++ b/pkg/tanka/parse_test.go @@ -112,7 +112,7 @@ func TestEvalJsonnet(t *testing.T) { } for _, test := range cases { - data, env, e := eval(test.baseDir, jsonnet.Opts{}) + data, env, e := ParseEnv(test.baseDir, jsonnet.Opts{}, DefaultEvaluator) if data == nil { assert.NoError(t, e) } else if e != nil { diff --git a/pkg/tanka/workflow.go b/pkg/tanka/workflow.go index ab301f64a..5da2027bb 100644 --- a/pkg/tanka/workflow.go +++ b/pkg/tanka/workflow.go @@ -184,7 +184,7 @@ func Show(baseDir string, opts Opts) (manifest.List, error) { // Eval returns the raw evaluated Jsonnet output (without any transformations) func Eval(dir string, opts Opts) (raw interface{}, err error) { - r, _, err := eval(dir, opts.JsonnetOpts) + r, _, err := ParseEnv(dir, opts.JsonnetOpts, DefaultEvaluator) switch err.(type) { case ErrNoEnv, ErrMultipleEnvs: return r, err