Repository: grafana/tanka
Pull request #613 (2021-09-27 12:51:04)

Allow partial matches in the --name option
Say I get this output:
```
$ tk diff tanka/environments/my-project
Error: found multiple Environments in 'tanka/environments/my-project':
 - environments/my-project/dev
 - environments/my-project/prod
 ```

 I can now rerun this command with `--name dev` or `--name ops` instead of the whole environment name
When more than one environment matches, the same message as before is given


Patch:
diff --git a/cmd/tk/flags.go b/cmd/tk/flags.go
index 77aae844..1b807c75 100644
--- a/cmd/tk/flags.go
+++ b/cmd/tk/flags.go
@@ -19,7 +19,7 @@ type workflowFlagVars struct {
 
 func workflowFlags(fs *pflag.FlagSet) *workflowFlagVars {
 	v := workflowFlagVars{}
-	fs.StringVar(&v.name, "name", "", "Selects an environment from inline environments")
+	fs.StringVar(&v.name, "name", "", "string that only a single inline environment contains in its name")
 	fs.StringSliceVarP(&v.targets, "target", "t", nil, "Regex filter on '<kind>/<name>'. See https://tanka.dev/output-filtering")
 	return &v
 }
diff --git a/cmd/tk/workflow.go b/cmd/tk/workflow.go
index 66f20ea3..f36546c4 100644
--- a/cmd/tk/workflow.go
+++ b/cmd/tk/workflow.go
@@ -60,7 +60,7 @@ func pruneCmd() *cli.Command {
 	var opts tanka.PruneOpts
 	cmd.Flags().BoolVar(&opts.Force, "force", false, "force deleting (kubectl delete --force)")
 	cmd.Flags().BoolVar(&opts.AutoApprove, "dangerous-auto-approve", false, "skip interactive approval. Only for automation!")
-	cmd.Flags().StringVar(&opts.Name, "name", "", "Selects an environment from inline environments")
+	cmd.Flags().StringVar(&opts.Name, "name", "", "string that only a single inline environment contains in its name")
 	getJsonnetOpts := jsonnetFlags(cmd.Flags())
 
 	cmd.Run = func(cmd *cli.Command, args []string) error {
diff --git a/docs/docs/inline-environments.mdx b/docs/docs/inline-environments.mdx
index 3c40d7d8..957bd54c 100644
--- a/docs/docs/inline-environments.mdx
+++ b/docs/docs/inline-environments.mdx
@@ -160,6 +160,10 @@ to deploy:
 ```bash
 $ tk apply --name environment/us-central1 environments/monitoring-stack/main.jsonnet
 $ tk diff --name environment/europe-west2 environments/monitoring-stack/main.jsonnet
+
+# Partial matches also work (if they match a single environment)
+$ tk apply --name us-central1 environments/monitoring-stack/main.jsonnet
+$ tk diff --name west2 environments/monitoring-stack/main.jsonnet
 ```
 
 For export, it is possible to use the same `--name` selector or you can do a
diff --git a/pkg/tanka/evaluators.go b/pkg/tanka/evaluators.go
index 3babc8c2..8ceaa9fb 100644
--- a/pkg/tanka/evaluators.go
+++ b/pkg/tanka/evaluators.go
@@ -125,7 +125,7 @@ local singleEnv(object) =
        && std.objectHas(object, 'kind')
     then
       if object.kind == 'Environment'
-      && object.metadata.name == '%s'
+      && std.member(object.metadata.name, '%s')
       then object
       else {}
     else
diff --git a/pkg/tanka/inline.go b/pkg/tanka/inline.go
index 6a85dfc9..de173715 100644
--- a/pkg/tanka/inline.go
+++ b/pkg/tanka/inline.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"path/filepath"
+	"sort"
 
 	"github.com/grafana/tanka/pkg/jsonnet/jpath"
 	"github.com/grafana/tanka/pkg/kubernetes/manifest"
@@ -37,11 +38,12 @@ func (i *InlineLoader) Load(path string, opts LoaderOpts) (*v1alpha1.Environment
 		for _, e := range envs {
 			names = append(names, e.Metadata().Name())
 		}
+		sort.Strings(names)
 		return nil, ErrMultipleEnvs{path, names}
 	}
 
 	if len(envs) == 0 {
-		return nil, fmt.Errorf("Found no matching environments; run 'tk env list %s' to view available options", path)
+		return nil, fmt.Errorf("found no matching environments; run 'tk env list %s' to view available options", path)
 	}
 
 	// TODO: Re-serializing the entire env here. This is horribly inefficient
diff --git a/pkg/tanka/load.go b/pkg/tanka/load.go
index d4b925bd..7cf928a8 100644
--- a/pkg/tanka/load.go
+++ b/pkg/tanka/load.go
@@ -31,6 +31,12 @@ func Load(path string, opts Opts) (*LoadResult, error) {
 		return nil, err
 	}
 
+	// Check if there are still any inline environments in the manifests
+	// They are not real k8s resources, and cannot be applied
+	if envs := process.Filter(result.Resources, process.MustStrExps("Environment/.*")); len(envs) > 0 {
+		return nil, errors.New("found a tanka Environment resource. Check that you aren't using a spec.json and inline environments simultaneously")
+	}
+
 	return result, nil
 }
 


Test patch:
diff --git a/pkg/tanka/load_test.go b/pkg/tanka/load_test.go
index 0863a01d..2345ca74 100644
--- a/pkg/tanka/load_test.go
+++ b/pkg/tanka/load_test.go
@@ -150,7 +150,29 @@ func TestLoad(t *testing.T) {
 	}
 }
 
-func TestLoadFailsWhenDuplicateEnv(t *testing.T) {
-	_, err := Load("./testdata/cases/withduplicateenv", Opts{Name: "withenv"})
-	assert.NotNil(t, err)
+func TestLoadSelectEnvironment(t *testing.T) {
+	// No match
+	_, err := Load("./testdata/cases/multiple-inline-envs", Opts{Name: "no match"})
+	assert.EqualError(t, err, "found no matching environments; run 'tk env list ./testdata/cases/multiple-inline-envs' to view available options")
+
+	// Empty options, match all environments
+	_, err = Load("./testdata/cases/multiple-inline-envs", Opts{})
+	assert.EqualError(t, err, "found multiple Environments in './testdata/cases/multiple-inline-envs': \n - project1-env1\n - project1-env2\n - project2-env1")
+
+	// Partial match two environments
+	_, err = Load("./testdata/cases/multiple-inline-envs", Opts{Name: "env1"})
+	assert.EqualError(t, err, "found multiple Environments in './testdata/cases/multiple-inline-envs': \n - project1-env1\n - project2-env1")
+
+	// Partial match
+	_, err = Load("./testdata/cases/multiple-inline-envs", Opts{Name: "project2"})
+	assert.NoError(t, err)
+
+	// Full match
+	_, err = Load("./testdata/cases/multiple-inline-envs", Opts{Name: "project1-env1"})
+	assert.NoError(t, err)
+}
+
+func TestLoadFailsWhenBothSpecAndInline(t *testing.T) {
+	_, err := Load("./testdata/cases/static_and_inline", Opts{Name: "inline"})
+	assert.EqualError(t, err, "found a tanka Environment resource. Check that you aren't using a spec.json and inline environments simultaneously")
 }
diff --git a/pkg/tanka/testdata/cases/withduplicateenv/main.jsonnet b/pkg/tanka/testdata/cases/multiple-inline-envs/main.jsonnet
similarity index 50%
rename from pkg/tanka/testdata/cases/withduplicateenv/main.jsonnet
rename to pkg/tanka/testdata/cases/multiple-inline-envs/main.jsonnet
index fa93bb95..4e0407e3 100644
--- a/pkg/tanka/testdata/cases/withduplicateenv/main.jsonnet
+++ b/pkg/tanka/testdata/cases/multiple-inline-envs/main.jsonnet
@@ -1,13 +1,13 @@
 {
-  env1: {
+  project1_env1: {
     apiVersion: 'tanka.dev/v1alpha1',
     kind: 'Environment',
     metadata: {
-      name: 'withenv',
+      name: 'project1-env1',
     },
     spec: {
       apiServer: 'https://localhost',
-      namespace: 'withenv',
+      namespace: 'project1-env1',
     },
     data: {
       apiVersion: 'v1',
@@ -15,15 +15,31 @@
       metadata: { name: 'config' },
     },
   },
-  env2: {
+  project1_env2: {
     apiVersion: 'tanka.dev/v1alpha1',
     kind: 'Environment',
     metadata: {
-      name: 'withenv',
+      name: 'project1-env2',
     },
     spec: {
       apiServer: 'https://localhost',
-      namespace: 'withenv',
+      namespace: 'project1-env2',
+    },
+    data: {
+      apiVersion: 'v1',
+      kind: 'ConfigMap',
+      metadata: { name: 'config' },
+    },
+  },
+  project2_env1: {
+    apiVersion: 'tanka.dev/v1alpha1',
+    kind: 'Environment',
+    metadata: {
+      name: 'project2-env1',
+    },
+    spec: {
+      apiServer: 'https://localhost',
+      namespace: 'project2-env1',
     },
     data: {
       apiVersion: 'v1',
diff --git a/pkg/tanka/testdata/cases/static_and_inline/main.jsonnet b/pkg/tanka/testdata/cases/static_and_inline/main.jsonnet
new file mode 100644
index 00000000..d89f8a09
--- /dev/null
+++ b/pkg/tanka/testdata/cases/static_and_inline/main.jsonnet
@@ -0,0 +1,16 @@
+{
+  apiVersion: 'tanka.dev/v1alpha1',
+  kind: 'Environment',
+  metadata: {
+    name: 'inline',
+  },
+  spec: {
+    apiServer: 'https://localhost',
+    namespace: 'inline',
+  },
+  data: {
+    apiVersion: 'v1',
+    kind: 'ConfigMap',
+    metadata: { name: 'config' },
+  },
+}
diff --git a/pkg/tanka/testdata/cases/static_and_inline/spec.json b/pkg/tanka/testdata/cases/static_and_inline/spec.json
new file mode 100644
index 00000000..279417a2
--- /dev/null
+++ b/pkg/tanka/testdata/cases/static_and_inline/spec.json
@@ -0,0 +1,11 @@
+{
+  "apiVersion": "tanka.dev/v1alpha1",
+  "kind": "Environment",
+  "metadata": {
+    "name": "static"
+  },
+  "spec": {
+    "apiServer": "https://localhost",
+    "namespace": "static"
+  }
+}