diff --git a/.gitignore b/.gitignore index 1521c8b76..517ab3db8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ dist +tk diff --git a/cmd/tk/env.go b/cmd/tk/env.go index a799dda8b..690b8bdb2 100644 --- a/cmd/tk/env.go +++ b/cmd/tk/env.go @@ -36,7 +36,7 @@ func envCmd() *cli.Command { return cmd } -func envSettingsFlags(env *v1alpha1.Config, fs *pflag.FlagSet) { +func envSettingsFlags(env *v1alpha1.Environment, fs *pflag.FlagSet) { fs.StringVar(&env.Spec.APIServer, "server", env.Spec.APIServer, "endpoint of the Kubernetes API") fs.StringVar(&env.Spec.APIServer, "server-from-context", env.Spec.APIServer, "set the server to a known one from $KUBECONFIG") fs.StringVar(&env.Spec.Namespace, "namespace", env.Spec.Namespace, "namespace to create objects in") @@ -61,7 +61,7 @@ func envSetCmd() *cli.Command { } // flags - tmp := v1alpha1.Config{} + tmp := v1alpha1.Environment{} envSettingsFlags(&tmp, cmd.Flags()) // removed name flag @@ -136,7 +136,7 @@ func envAddCmd() *cli.Command { } // used by initCmd() as well -func addEnv(dir string, cfg *v1alpha1.Config) error { +func addEnv(dir string, cfg *v1alpha1.Environment) error { path, err := filepath.Abs(dir) if err != nil { return err @@ -216,7 +216,7 @@ func envListCmd() *cli.Command { useNames := cmd.Flags().Bool("names", false, "plain names output") cmd.Run = func(cmd *cli.Command, args []string) error { - envs := []v1alpha1.Config{} + envs := []v1alpha1.Environment{} dirs := findBaseDirs() var selector labels.Selector var err error diff --git a/cmd/tk/jsonnet.go b/cmd/tk/jsonnet.go index 8f173c564..cb578835b 100644 --- a/cmd/tk/jsonnet.go +++ b/cmd/tk/jsonnet.go @@ -30,7 +30,7 @@ func evalCmd() *cli.Command { jsonnetOpts.EvalPattern = *evalPattern raw, err := tanka.Eval(args[0], jsonnetOpts) - if err != nil { + if raw == nil && err != nil { return err } diff --git a/cmd/tk/main.go b/cmd/tk/main.go index 15ddd3a79..a15afbfca 100644 --- a/cmd/tk/main.go +++ b/cmd/tk/main.go @@ -10,6 +10,7 @@ import ( "github.com/go-clix/cli" + "github.com/grafana/tanka/pkg/jsonnet" "github.com/grafana/tanka/pkg/jsonnet/jpath" "github.com/grafana/tanka/pkg/spec" "github.com/grafana/tanka/pkg/spec/v1alpha1" @@ -60,7 +61,7 @@ func main() { } } -func setupConfiguration(baseDir string) *v1alpha1.Config { +func setupConfiguration(baseDir string) *v1alpha1.Environment { _, baseDir, rootDir, err := jpath.Resolve(baseDir) if err != nil { log.Fatalln("Resolving jpath:", err) @@ -77,6 +78,18 @@ func setupConfiguration(baseDir string) *v1alpha1.Config { if verbose { fmt.Print(err) } + // no spec.json is found, try parsing main.jsonnet + case spec.ErrNoSpec: + config, err := tanka.EvalEnvs(baseDir, jsonnet.Opts{}) + if err != nil { + switch err.(type) { + case tanka.ErrNoEnv: + return nil + default: + log.Fatalf("Reading main.jsonnet: %s", err) + } + } + return config // some other error default: log.Fatalf("Reading spec.json: %s", err) diff --git a/cmd/tk/other.go b/cmd/tk/other.go index 40dc694fe..97558abe4 100644 --- a/cmd/tk/other.go +++ b/cmd/tk/other.go @@ -20,7 +20,7 @@ func findBaseDirs() (dirs []string) { } if err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { - requiredFiles := []string{"main.jsonnet", "spec.json"} + requiredFiles := []string{"main.jsonnet"} for _, name := range requiredFiles { if _, err := os.Stat(filepath.Join(path, name)); err != nil { // missing file, not a valid environment directory diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index 2e4f07c51..783ec047d 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -12,7 +12,7 @@ import ( // Kubernetes exposes methods to work with the Kubernetes orchestrator type Kubernetes struct { - Env v1alpha1.Config + Env v1alpha1.Environment // Client (kubectl) ctl client.Client @@ -26,7 +26,7 @@ type Kubernetes struct { type Differ func(manifest.List) (*string, error) // New creates a new Kubernetes with an initialized client -func New(env v1alpha1.Config) (*Kubernetes, error) { +func New(env v1alpha1.Environment) (*Kubernetes, error) { // setup client ctl, err := client.New(env.Spec.APIServer) if err != nil { diff --git a/pkg/process/process.go b/pkg/process/process.go index c14ae8b11..a72f9aec7 100644 --- a/pkg/process/process.go +++ b/pkg/process/process.go @@ -18,7 +18,7 @@ const ( // - tanka.dev/** labels // - filtering // - best-effort sorting -func Process(raw interface{}, cfg v1alpha1.Config, exprs Matchers) (manifest.List, error) { +func Process(raw interface{}, cfg v1alpha1.Environment, exprs Matchers) (manifest.List, error) { // Scan for everything that looks like a Kubernetes object extracted, err := Extract(raw) if err != nil { @@ -56,7 +56,7 @@ func Process(raw interface{}, cfg v1alpha1.Config, exprs Matchers) (manifest.Lis } // Label conditionally adds tanka.dev/** labels to each manifest in the List -func Label(list manifest.List, cfg v1alpha1.Config) manifest.List { +func Label(list manifest.List, cfg v1alpha1.Environment) manifest.List { for i, m := range list { // inject tanka.dev/environment label if cfg.Spec.InjectLabels { @@ -68,7 +68,7 @@ func Label(list manifest.List, cfg v1alpha1.Config) manifest.List { return list } -func ResourceDefaults(list manifest.List, cfg v1alpha1.Config) manifest.List { +func ResourceDefaults(list manifest.List, cfg v1alpha1.Environment) manifest.List { for i, m := range list { for k, v := range cfg.Spec.ResourceDefaults.Annotations { annotations := m.Metadata().Annotations() diff --git a/pkg/process/resourceDefaults_test.go b/pkg/process/resourceDefaults_test.go index bed46b584..5b3272962 100644 --- a/pkg/process/resourceDefaults_test.go +++ b/pkg/process/resourceDefaults_test.go @@ -55,7 +55,7 @@ func TestResourceDefaults(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { - cfg := v1alpha1.Config{ + cfg := v1alpha1.Environment{ Spec: v1alpha1.Spec{ ResourceDefaults: v1alpha1.ResourceDefaults{ Annotations: c.specAnnotations, diff --git a/pkg/spec/depreciations_test.go b/pkg/spec/depreciations_test.go index 88621db49..2644a0ff3 100644 --- a/pkg/spec/depreciations_test.go +++ b/pkg/spec/depreciations_test.go @@ -14,6 +14,9 @@ import ( func TestDeprecated(t *testing.T) { data := []byte(` { + "metadata": { + "name": "test" + }, "spec": { "namespace": "new" }, @@ -23,7 +26,7 @@ func TestDeprecated(t *testing.T) { } `) - got, err := Parse(data, "test") + got, err := Parse(data) require.Equal(t, ErrDeprecated{ {old: "server", new: "spec.apiServer"}, {old: "team", new: "metadata.labels.team"}, diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 989c48950..93171e8b2 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -18,9 +18,9 @@ const APIGroup = "tanka.dev" // Specfile is the filename for the environment config const Specfile = "spec.json" -// ParseDir parses the given environments `spec.json` into a `v1alpha1.Config` +// ParseDir parses the given environments `spec.json` into a `v1alpha1.Environment` // object with the name set to the directories name -func ParseDir(baseDir, name string) (*v1alpha1.Config, error) { +func ParseDir(baseDir, name string) (*v1alpha1.Environment, error) { fi, err := os.Stat(baseDir) if err != nil { return nil, err @@ -39,20 +39,22 @@ func ParseDir(baseDir, name string) (*v1alpha1.Config, error) { return nil, err } - return Parse(data, name) + c, err := Parse(data) + if c != nil { + // set the name field + c.Metadata.Name = name + } + + return c, err } -// Parse parses the json `data` into a `v1alpha1.Config` object. -// `name` is the name of the environment -func Parse(data []byte, name string) (*v1alpha1.Config, error) { +// Parse parses the json `data` into a `v1alpha1.Environment` object. +func Parse(data []byte) (*v1alpha1.Environment, error) { config := v1alpha1.New() if err := json.Unmarshal(data, config); err != nil { return nil, errors.Wrap(err, "parsing spec.json") } - // set the name field - config.Metadata.Name = name - if err := handleDeprecated(config, data); err != nil { return config, err } @@ -65,7 +67,7 @@ func Parse(data []byte, name string) (*v1alpha1.Config, error) { return config, nil } -func handleDeprecated(c *v1alpha1.Config, data []byte) error { +func handleDeprecated(c *v1alpha1.Environment, data []byte) error { var errDepr ErrDeprecated var msi map[string]interface{} diff --git a/pkg/spec/v1alpha1/config.go b/pkg/spec/v1alpha1/environment.go similarity index 78% rename from pkg/spec/v1alpha1/config.go rename to pkg/spec/v1alpha1/environment.go index 77ce0296b..e6031ed0b 100644 --- a/pkg/spec/v1alpha1/config.go +++ b/pkg/spec/v1alpha1/environment.go @@ -2,9 +2,9 @@ package v1alpha1 import "strings" -// New creates a new Config object with internal values already set -func New() *Config { - c := Config{} +// New creates a new Environment object with internal values already set +func New() *Environment { + c := Environment{} // constants c.APIVersion = "tanka.dev/v1alpha1" @@ -18,13 +18,13 @@ func New() *Config { return &c } -// Config holds the configuration variables for config version v1alpha1 -// ApiVersion and Kind are currently unused, this may change in the future. -type Config struct { - APIVersion string `json:"apiVersion"` - Kind string `json:"kind"` - Metadata Metadata `json:"metadata"` - Spec Spec `json:"spec"` +// Environment represents a set of resources in relation to its Kubernetes cluster +type Environment struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Metadata Metadata `json:"metadata"` + Spec Spec `json:"spec"` + Data interface{} `json:"data,omitempty"` } // Metadata is meant for humans and not parsed diff --git a/pkg/tanka/errors.go b/pkg/tanka/errors.go new file mode 100644 index 000000000..546285b09 --- /dev/null +++ b/pkg/tanka/errors.go @@ -0,0 +1,26 @@ +package tanka + +import ( + "fmt" + "strings" +) + +// ErrNoEnv means that the given jsonnet has no Environment object +// This must not be fatal, some operations work without +type ErrNoEnv struct { + path string +} + +func (e ErrNoEnv) Error() string { + return fmt.Sprintf("unable to find an Environment in '%s'", e.path) +} + +// ErrMultipleEnvs means that the given jsonnet has multiple Environment objects +type ErrMultipleEnvs struct { + path string + names []string +} + +func (e ErrMultipleEnvs) Error() string { + return fmt.Sprintf("found multiple Environments (%s) in '%s'", strings.Join(e.names, ", "), e.path) +} diff --git a/pkg/tanka/parse.go b/pkg/tanka/parse.go index f408d9ab3..2cc87e815 100644 --- a/pkg/tanka/parse.go +++ b/pkg/tanka/parse.go @@ -26,6 +26,7 @@ const DEFAULT_DEV_VERSION = "dev" var CURRENT_VERSION = DEFAULT_DEV_VERSION // loaded is the final result of all processing stages: +// TODO: remove or update this summary // 1. jpath.Resolve: Consruct import paths // 2. parseSpec: load spec.json // 3. evalJsonnet: evaluate Jsonnet to JSON @@ -33,7 +34,7 @@ var CURRENT_VERSION = DEFAULT_DEV_VERSION // // Also connect() is provided to connect to the cluster for live operations type loaded struct { - Env *v1alpha1.Config + Env *v1alpha1.Environment Resources manifest.List } @@ -64,16 +65,24 @@ 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) { - raw, env, err := eval(path, opts.JsonnetOpts) + _, env, err := eval(path, opts.JsonnetOpts) if err != nil { return nil, err } + if env == nil { + return nil, fmt.Errorf("no Tanka environment found") + } + + if env.Metadata.Name == "" { + return nil, fmt.Errorf("Environment has no metadata.name set in %s", path) + } + if err := checkVersion(env.Spec.ExpectVersions.Tanka); err != nil { return nil, err } - rec, err := process.Process(raw, *env, opts.Filters) + rec, err := process.Process(env.Data, *env, opts.Filters) if err != nil { return nil, err } @@ -84,25 +93,39 @@ func load(path string, opts Opts) (*loaded, error) { }, nil } -// eval runs all processing stages describe at the Processed type apart from -// post-processing, thus returning the raw Jsonnet result. -func eval(path string, opts jsonnet.Opts) (raw interface{}, env *v1alpha1.Config, err error) { - env, err = parseSpec(path) - if err != nil { - return nil, nil, err - } - - raw, err = evalJsonnet(path, env, opts) - if err != nil { - return nil, nil, errors.Wrap(err, "evaluating jsonnet") - } +// 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 + } - return raw, env, nil + // 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 + }, + ) } -// parseEnv parses the `spec.json` of the environment and returns a +// parseSpec parses the `spec.json` of the environment and returns a // *kubernetes.Kubernetes from it -func parseSpec(path string) (*v1alpha1.Config, error) { +func parseSpec(path string) (*v1alpha1.Environment, error) { _, baseDir, rootDir, err := jpath.Resolve(path) if err != nil { return nil, errors.Wrap(err, "resolving jpath") @@ -119,7 +142,7 @@ func parseSpec(path string) (*v1alpha1.Config, error) { log.Println(err) // spec.json missing. we can still work with the default value case spec.ErrNoSpec: - return config, nil + return config, err // some other error default: return nil, errors.Wrap(err, "reading spec.json") @@ -129,40 +152,78 @@ func parseSpec(path string) (*v1alpha1.Config, error) { return config, nil } -// evalJsonnet evaluates the jsonnet environment at the given path -func evalJsonnet(path string, env *v1alpha1.Config, opts jsonnet.Opts) (interface{}, error) { - // make env spec accessible from Jsonnet - jsonEnv, err := json.Marshal(env) +type evaluateFunc func(path string, opts jsonnet.Opts) (string, error) + +// parseEnv finds the Environment object at the given file system path +func parseEnv(path string, opts jsonnet.Opts, evalFn evaluateFunc) (interface{}, *v1alpha1.Environment, error) { + specEnv, err := parseSpec(path) if err != nil { - return nil, errors.Wrap(err, "marshalling environment config") + switch err.(type) { + case spec.ErrNoSpec: + specEnv = nil + default: + return nil, nil, errors.Wrap(err, "reading spec.json") + } + } else { + // original behavior, if env has spec.json + // then make env spec accessible through extCode + jsonEnv, err := json.Marshal(specEnv) + if err != nil { + return nil, nil, errors.Wrap(err, "marshalling environment config") + } + opts.ExtCode.Set(spec.APIGroup+"/environment", string(jsonEnv)) } - opts.ExtCode.Set(spec.APIGroup+"/environment", string(jsonEnv)) - // evaluate Jsonnet - var raw string - entrypoint, err := jpath.Entrypoint(path) + raw, err := evalFn(path, opts) if err != nil { - return nil, err + return nil, nil, err + } + + var data interface{} + if err := json.Unmarshal([]byte(raw), &data); err != nil { + return nil, nil, errors.Wrap(err, "unmarshalling data") } if opts.EvalPattern != "" { - evalScript := fmt.Sprintf("(import '%s').%s", entrypoint, opts.EvalPattern) - raw, err = jsonnet.Evaluate(entrypoint, evalScript, opts) + // EvalPattern has no affinity with an environment, behave as jsonnet interpreter + return data, nil, nil + } + + extractedEnvs, err := extractEnvironments(data) + if _, ok := err.(process.ErrorPrimitiveReached); ok { + if specEnv == nil { + // if no environments or spec found, behave as jsonnet interpreter + return data, nil, ErrNoEnv{path} + } + } else if err != nil { + return nil, nil, err + } + + var env *v1alpha1.Environment + + if len(extractedEnvs) > 1 { + names := make([]string, 0) + for _, exEnv := range extractedEnvs { + names = append(names, exEnv.Metadata().Name()) + } + return data, nil, ErrMultipleEnvs{path, names} + } else if len(extractedEnvs) == 1 { + marshalled, err := json.Marshal(extractedEnvs[0]) if err != nil { - return nil, err + return nil, nil, err } - } else { - raw, err = jsonnet.EvaluateFile(entrypoint, opts) + env, err = spec.Parse(marshalled) if err != nil { - return nil, err + return nil, nil, err } + return data, env, nil + } else if specEnv != nil { + // if no environments found, fallback to original behavior + specEnv.Data = data + return data, specEnv, nil } - // parse result - var data interface{} - if err := json.Unmarshal([]byte(raw), &data); err != nil { - return nil, err - } - return data, nil + // if no environments or spec found, behave as jsonnet interpreter + return data, nil, ErrNoEnv{path} } func checkVersion(constraint string) error { @@ -189,3 +250,78 @@ func checkVersion(constraint string) error { return nil } + +func extractEnvironments(data interface{}) (manifest.List, error) { + // Scan for everything that looks like a Kubernetes object + extracted, err := process.Extract(data) + if err != nil { + return nil, err + } + + // Unwrap *List types + if err := process.Unwrap(extracted); err != nil { + return nil, err + } + + out := make(manifest.List, 0, len(extracted)) + for _, m := range extracted { + out = append(out, m) + } + + // 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 2715e0e08..ad3d5ac5b 100644 --- a/pkg/tanka/parse_test.go +++ b/pkg/tanka/parse_test.go @@ -12,6 +12,7 @@ func TestEvalJsonnet(t *testing.T) { cases := []struct { baseDir string expected interface{} + env *v1alpha1.Environment }{ { baseDir: "./testdata/cases/array/", @@ -25,18 +26,99 @@ func TestEvalJsonnet(t *testing.T) { map[string]interface{}{"testCase": "nestedArray[1][1]"}, }, }, + env: nil, }, { baseDir: "./testdata/cases/object/", expected: map[string]interface{}{ "testCase": "object", }, + env: nil, + }, + { + baseDir: "./testdata/cases/withspecjson/", + expected: map[string]interface{}{ + "testCase": "object", + }, + env: &v1alpha1.Environment{ + APIVersion: v1alpha1.New().APIVersion, + Kind: v1alpha1.New().Kind, + Metadata: v1alpha1.Metadata{ + Name: "cases/withspecjson", + Labels: v1alpha1.New().Metadata.Labels, + }, + Spec: v1alpha1.Spec{ + APIServer: "https://localhost", + Namespace: "withspec", + }, + Data: map[string]interface{}{ + "testCase": "object", + }, + }, + }, + { + baseDir: "./testdata/cases/withspecjson/main.jsonnet", + expected: map[string]interface{}{ + "testCase": "object", + }, + env: &v1alpha1.Environment{ + APIVersion: v1alpha1.New().APIVersion, + Kind: v1alpha1.New().Kind, + Metadata: v1alpha1.Metadata{ + Name: "cases/withspecjson", + Labels: v1alpha1.New().Metadata.Labels, + }, + Spec: v1alpha1.Spec{ + APIServer: "https://localhost", + Namespace: "withspec", + }, + Data: map[string]interface{}{ + "testCase": "object", + }, + }, + }, + { + baseDir: "./testdata/cases/withenv/main.jsonnet", + expected: map[string]interface{}{ + "apiVersion": v1alpha1.New().APIVersion, + "kind": v1alpha1.New().Kind, + "metadata": map[string]interface{}{ + "name": "withenv", + }, + "spec": map[string]interface{}{ + "apiServer": "https://localhost", + "namespace": "withenv", + }, + "data": map[string]interface{}{ + "testCase": "object", + }, + }, + env: &v1alpha1.Environment{ + APIVersion: v1alpha1.New().APIVersion, + Kind: v1alpha1.New().Kind, + Metadata: v1alpha1.Metadata{ + Name: "withenv", + Labels: v1alpha1.New().Metadata.Labels, + }, + Spec: v1alpha1.Spec{ + APIServer: "https://localhost", + Namespace: "withenv", + }, + Data: map[string]interface{}{ + "testCase": "object", + }, + }, }, } for _, test := range cases { - data, e := evalJsonnet(test.baseDir, v1alpha1.New(), jsonnet.Opts{}) - assert.NoError(t, e) + data, env, e := eval(test.baseDir, jsonnet.Opts{}) + if data == nil { + assert.NoError(t, e) + } else if e != nil { + assert.IsType(t, ErrNoEnv{}, e) + } assert.Equal(t, test.expected, data) + assert.Equal(t, test.env, env) } } diff --git a/pkg/tanka/status.go b/pkg/tanka/status.go index c9e22d890..ceccc269a 100644 --- a/pkg/tanka/status.go +++ b/pkg/tanka/status.go @@ -10,7 +10,7 @@ import ( // the individual resources of the desired state and also the status of the // client. type Info struct { - Env *v1alpha1.Config + Env *v1alpha1.Environment Resources manifest.List Client client.Info } diff --git a/pkg/tanka/testdata/cases/withenv/main.jsonnet b/pkg/tanka/testdata/cases/withenv/main.jsonnet new file mode 100644 index 000000000..d6f4b7c2a --- /dev/null +++ b/pkg/tanka/testdata/cases/withenv/main.jsonnet @@ -0,0 +1,14 @@ +{ + apiVersion: 'tanka.dev/v1alpha1', + kind: 'Environment', + metadata: { + name: 'withenv', + }, + spec: { + apiServer: 'https://localhost', + namespace: 'withenv', + }, + data: { + testCase: 'object', + }, +} diff --git a/pkg/tanka/testdata/cases/withspecjson/main.jsonnet b/pkg/tanka/testdata/cases/withspecjson/main.jsonnet new file mode 100644 index 000000000..157b04a42 --- /dev/null +++ b/pkg/tanka/testdata/cases/withspecjson/main.jsonnet @@ -0,0 +1,3 @@ +{ + testCase: 'object', +} diff --git a/pkg/tanka/testdata/cases/withspecjson/spec.json b/pkg/tanka/testdata/cases/withspecjson/spec.json new file mode 100644 index 000000000..9c70d6b21 --- /dev/null +++ b/pkg/tanka/testdata/cases/withspecjson/spec.json @@ -0,0 +1,11 @@ +{ + "apiVersion": "tanka.dev/v1alpha1", + "kind": "Environment", + "metadata": { + "name": "withspec" + }, + "spec": { + "apiServer": "https://localhost", + "namespace": "withspec" + } +} diff --git a/pkg/tanka/workflow.go b/pkg/tanka/workflow.go index bbe8f51e6..ab301f64a 100644 --- a/pkg/tanka/workflow.go +++ b/pkg/tanka/workflow.go @@ -185,8 +185,12 @@ 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) - if err != nil { + switch err.(type) { + case ErrNoEnv, ErrMultipleEnvs: + return r, err + case nil: + return r, nil + default: return nil, err } - return r, nil }