Repository: grafana/tanka
Pull request #472 (2021-01-08 10:25:26)

fix: correct namespace when using a file
This is an edge case, `namespace` should match the full execution path if `path` is a file that is not `main.jsonnet`.


Patch:
diff --git a/cmd/tk/export.go b/cmd/tk/export.go
index 78a90021..babf5631 100644
--- a/cmd/tk/export.go
+++ b/cmd/tk/export.go
@@ -47,7 +47,7 @@ func exportCmd() *cli.Command {
 			Extension: *extension,
 			Merge:     *merge,
 			Targets:   vars.targets,
-			ParseParallelOpts: tanka.ParseParallelOpts{
+			ParallelOpts: tanka.ParallelOpts{
 				JsonnetOpts: getJsonnetOpts(),
 				Selector:    getLabelSelector(),
 			},
@@ -63,7 +63,7 @@ func exportCmd() *cli.Command {
 				}
 
 				// get absolute path to Environment
-				envs, err := tanka.ListEnvs(path, tanka.ListOpts{Selector: opts.ParseParallelOpts.Selector})
+				envs, err := tanka.ListEnvs(path, tanka.ListOpts{Selector: opts.ParallelOpts.Selector})
 				if err != nil {
 					return err
 				}
@@ -75,7 +75,7 @@ func exportCmd() *cli.Command {
 			}
 
 			// validate environment
-			if _, err := tanka.Peek(path, opts.ParseParallelOpts.JsonnetOpts); err != nil {
+			if _, err := tanka.Peek(path, opts.ParallelOpts.JsonnetOpts); err != nil {
 				return err
 			}
 
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 42ae491f..2c85ba06 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/pkg/errors"
 
+	"github.com/grafana/tanka/pkg/jsonnet/jpath"
 	"github.com/grafana/tanka/pkg/spec/v1alpha1"
 )
 
@@ -20,30 +21,43 @@ const Specfile = "spec.json"
 
 // ParseDir parses the given environments `spec.json` into a `v1alpha1.Environment`
 // object with the name set to the directories name
-func ParseDir(baseDir, path string) (*v1alpha1.Environment, error) {
-	fi, err := os.Stat(baseDir)
+func ParseDir(path string) (*v1alpha1.Environment, error) {
+	root, base, err := jpath.Dirs(path)
 	if err != nil {
 		return nil, err
 	}
-	if !fi.IsDir() {
-		return nil, errors.New("baseDir is not an directory")
+
+	// name of the environment: relative path from rootDir
+	name, err := filepath.Rel(root, base)
+	if err != nil {
+		return nil, err
+	}
+
+	file, err := jpath.Entrypoint(path)
+	if err != nil {
+		return nil, err
+	}
+
+	namespace, err := filepath.Rel(root, file)
+	if err != nil {
+		return nil, err
 	}
 
-	data, err := ioutil.ReadFile(filepath.Join(baseDir, Specfile))
+	data, err := ioutil.ReadFile(filepath.Join(base, Specfile))
 	if err != nil {
 		if os.IsNotExist(err) {
 			c := v1alpha1.New()
-			c.Metadata.Name = path // legacy behavior
-			c.Metadata.Namespace = path
+			c.Metadata.Name = name // legacy behavior
+			c.Metadata.Namespace = namespace
 			return c, ErrNoSpec{path}
 		}
 		return nil, err
 	}
 
-	c, err := Parse(data, path)
+	c, err := Parse(data, namespace)
 	if c != nil {
 		// set the name field
-		c.Metadata.Name = path // legacy behavior
+		c.Metadata.Name = name // legacy behavior
 	}
 
 	return c, err
diff --git a/pkg/tanka/errors.go b/pkg/tanka/errors.go
index 5d0c72a4..8422b55a 100644
--- a/pkg/tanka/errors.go
+++ b/pkg/tanka/errors.go
@@ -25,12 +25,12 @@ func (e ErrMultipleEnvs) Error() string {
 	return fmt.Sprintf("found multiple Environments (%s) in '%s'", strings.Join(e.names, ", "), e.path)
 }
 
-// ErrParseParallel is an array of errors collected while parsing environments in parallel
-type ErrParseParallel struct {
+// ErrParallel is an array of errors collected while parsing environments in parallel
+type ErrParallel struct {
 	errors []error
 }
 
-func (e ErrParseParallel) Error() string {
+func (e ErrParallel) Error() string {
 	returnErr := fmt.Sprintf("Unable to parse selected Environments:\n\n")
 	for _, err := range e.errors {
 		returnErr = fmt.Sprintf("%s- %s\n", returnErr, err.Error())
diff --git a/pkg/tanka/export.go b/pkg/tanka/export.go
index 0fc4c3ec..73eca513 100644
--- a/pkg/tanka/export.go
+++ b/pkg/tanka/export.go
@@ -38,7 +38,7 @@ type ExportEnvOpts struct {
 	// optional: only export specified Kubernetes manifests
 	Targets []string
 	// optional: options for parsing Environments
-	ParseParallelOpts ParseParallelOpts
+	ParallelOpts ParallelOpts
 }
 
 func ExportEnvironments(paths []string, to string, opts *ExportEnvOpts) error {
@@ -55,7 +55,7 @@ func ExportEnvironments(paths []string, to string, opts *ExportEnvOpts) error {
 	}
 
 	// get all environments for paths
-	envs, err := ParseParallel(paths, opts.ParseParallelOpts)
+	envs, err := parallelLoadEnvironments(paths, opts.ParallelOpts)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/tanka/inline.go b/pkg/tanka/inline.go
index b418157d..4137b4c6 100644
--- a/pkg/tanka/inline.go
+++ b/pkg/tanka/inline.go
@@ -45,12 +45,17 @@ func (i *InlineLoader) Load(path string, opts JsonnetOpts) (*v1alpha1.Environmen
 		return nil, fmt.Errorf("Found no environments in '%s'", path)
 	}
 
-	root, base, err := jpath.Dirs(path)
+	root, err := jpath.FindRoot(path)
 	if err != nil {
 		return nil, err
 	}
 
-	name, err := filepath.Rel(root, base)
+	file, err := jpath.Entrypoint(path)
+	if err != nil {
+		return nil, err
+	}
+
+	namespace, err := filepath.Rel(root, file)
 	if err != nil {
 		return nil, err
 	}
@@ -61,7 +66,7 @@ func (i *InlineLoader) Load(path string, opts JsonnetOpts) (*v1alpha1.Environmen
 		return nil, err
 	}
 
-	env, err := spec.Parse(envData, name)
+	env, err := spec.Parse(envData, namespace)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/tanka/environments.go b/pkg/tanka/parallel.go
similarity index 74%
rename from pkg/tanka/environments.go
rename to pkg/tanka/parallel.go
index 3181e901..e3f4bfb4 100644
--- a/pkg/tanka/environments.go
+++ b/pkg/tanka/parallel.go
@@ -9,10 +9,10 @@ import (
 
 const parallel = 8
 
-// ParseParallel evaluates multiple environments in parallel
-func ParseParallel(paths []string, opts ParseParallelOpts) (envs []*v1alpha1.Environment, err error) {
+// parallelLoadEnvironments evaluates multiple environments in parallel
+func parallelLoadEnvironments(paths []string, opts ParallelOpts) (envs []*v1alpha1.Environment, err error) {
 	wg := sync.WaitGroup{}
-	envsChan := make(chan parseJob)
+	envsChan := make(chan parallelJob)
 	var allErrors []error
 
 	numParallel := parallel
@@ -23,7 +23,7 @@ func ParseParallel(paths []string, opts ParseParallelOpts) (envs []*v1alpha1.Env
 		wg.Add(1)
 		go func() {
 			defer wg.Done()
-			errs := parseWorker(envsChan)
+			errs := parallelWorker(envsChan)
 			if errs != nil {
 				allErrors = append(allErrors, errs...)
 			}
@@ -35,7 +35,7 @@ func ParseParallel(paths []string, opts ParseParallelOpts) (envs []*v1alpha1.Env
 	for _, path := range paths {
 		env := &v1alpha1.Environment{}
 		results = append(results, env)
-		envsChan <- parseJob{
+		envsChan <- parallelJob{
 			path: path,
 			opts: opts.JsonnetOpts,
 			env:  env,
@@ -54,19 +54,19 @@ func ParseParallel(paths []string, opts ParseParallelOpts) (envs []*v1alpha1.Env
 	}
 
 	if len(allErrors) != 0 {
-		return envs, ErrParseParallel{errors: allErrors}
+		return envs, ErrParallel{errors: allErrors}
 	}
 
 	return envs, nil
 }
 
-type parseJob struct {
+type parallelJob struct {
 	path string
 	opts JsonnetOpts
 	env  *v1alpha1.Environment
 }
 
-func parseWorker(envsChan <-chan parseJob) (errs []error) {
+func parallelWorker(envsChan <-chan parallelJob) (errs []error) {
 	for req := range envsChan {
 		env, err := LoadEnvironment(req.path, Opts{JsonnetOpts: req.opts})
 		if err != nil {
diff --git a/pkg/tanka/static.go b/pkg/tanka/static.go
index 72ca4e57..445ca0f9 100644
--- a/pkg/tanka/static.go
+++ b/pkg/tanka/static.go
@@ -3,9 +3,7 @@ package tanka
 import (
 	"encoding/json"
 	"log"
-	"path/filepath"
 
-	"github.com/grafana/tanka/pkg/jsonnet/jpath"
 	"github.com/grafana/tanka/pkg/spec"
 	"github.com/grafana/tanka/pkg/spec/v1alpha1"
 )
@@ -33,12 +31,7 @@ func (s StaticLoader) Load(path string, opts JsonnetOpts) (*v1alpha1.Environment
 }
 
 func (s StaticLoader) Peek(path string, opts JsonnetOpts) (*v1alpha1.Environment, error) {
-	root, base, err := jpath.Dirs(path)
-	if err != nil {
-		return nil, err
-	}
-
-	config, err := parseStaticSpec(root, base)
+	config, err := parseStaticSpec(path)
 	if err != nil {
 		return nil, err
 	}
@@ -48,14 +41,8 @@ func (s StaticLoader) Peek(path string, opts JsonnetOpts) (*v1alpha1.Environment
 
 // parseStaticSpec parses the `spec.json` of the environment and returns a
 // *kubernetes.Kubernetes from it
-func parseStaticSpec(root, base string) (*v1alpha1.Environment, error) {
-	// name of the environment: relative path from rootDir
-	name, err := filepath.Rel(root, base)
-	if err != nil {
-		return nil, err
-	}
-
-	env, err := spec.ParseDir(base, name)
+func parseStaticSpec(path string) (*v1alpha1.Environment, error) {
+	env, err := spec.ParseDir(path)
 	if err != nil {
 		switch err.(type) {
 		// the config includes deprecated fields
diff --git a/pkg/tanka/tanka.go b/pkg/tanka/tanka.go
index ab014462..710ecccc 100644
--- a/pkg/tanka/tanka.go
+++ b/pkg/tanka/tanka.go
@@ -56,7 +56,7 @@ func checkVersion(constraint string) error {
 	return nil
 }
 
-type ParseParallelOpts struct {
+type ParallelOpts struct {
 	JsonnetOpts JsonnetOpts
 	Selector    labels.Selector
 	Parallel    int


Test patch:
diff --git a/pkg/tanka/load_test.go b/pkg/tanka/load_test.go
index 36084d61..0eab597b 100644
--- a/pkg/tanka/load_test.go
+++ b/pkg/tanka/load_test.go
@@ -32,7 +32,7 @@ func TestLoad(t *testing.T) {
 				Kind:       v1alpha1.New().Kind,
 				Metadata: v1alpha1.Metadata{
 					Name:      "cases/withspecjson",
-					Namespace: "cases/withspecjson",
+					Namespace: "cases/withspecjson/main.jsonnet",
 					Labels:    v1alpha1.New().Metadata.Labels,
 				},
 				Spec: v1alpha1.Spec{
@@ -62,7 +62,7 @@ func TestLoad(t *testing.T) {
 				Kind:       v1alpha1.New().Kind,
 				Metadata: v1alpha1.Metadata{
 					Name:      "cases/withspecjson",
-					Namespace: "cases/withspecjson",
+					Namespace: "cases/withspecjson/main.jsonnet",
 					Labels:    v1alpha1.New().Metadata.Labels,
 				},
 				Spec: v1alpha1.Spec{
@@ -79,6 +79,36 @@ func TestLoad(t *testing.T) {
 
 		{
 			name:    "inline",
+			baseDir: "./testdata/cases/withenv/",
+			expected: manifest.List{{
+				"apiVersion": "v1",
+				"kind":       "ConfigMap",
+				"metadata": map[string]interface{}{
+					"name":      "config",
+					"namespace": "withenv",
+				},
+			}},
+			env: &v1alpha1.Environment{
+				APIVersion: v1alpha1.New().APIVersion,
+				Kind:       v1alpha1.New().Kind,
+				Metadata: v1alpha1.Metadata{
+					Name:      "withenv",
+					Namespace: "cases/withenv/main.jsonnet",
+					Labels:    v1alpha1.New().Metadata.Labels,
+				},
+				Spec: v1alpha1.Spec{
+					APIServer: "https://localhost",
+					Namespace: "withenv",
+				},
+				Data: map[string]interface{}{
+					"apiVersion": "v1",
+					"kind":       "ConfigMap",
+					"metadata":   map[string]interface{}{"name": "config", "namespace": "withenv"},
+				},
+			},
+		},
+		{
+			name:    "inline-filename",
 			baseDir: "./testdata/cases/withenv/main.jsonnet",
 			expected: manifest.List{{
 				"apiVersion": "v1",
@@ -93,7 +123,7 @@ func TestLoad(t *testing.T) {
 				Kind:       v1alpha1.New().Kind,
 				Metadata: v1alpha1.Metadata{
 					Name:      "withenv",
-					Namespace: "cases/withenv",
+					Namespace: "cases/withenv/main.jsonnet",
 					Labels:    v1alpha1.New().Metadata.Labels,
 				},
 				Spec: v1alpha1.Spec{