Repository: grafana/tanka (go)

Issue:
Unexpected filename when using export with kubernetes recommended labels
I'm trying to use `tanka export` with a `--format` string that references a [Kubernetes common label](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) `app.kubernetes.io/name`.

I've verified that 

```
{{index .metadata.labels "app.kubernetes.io/name"}}
```
is valid and works with golang templates, but from tanka I just get `<no value>`.

Pull request description:
fix: make export --format respect "/" in template actions
During `tk export` path separators are temporarily replaced with `BEL` char in order to protect paths from being mixed with template output. But when there is a slash in a template action (e.g. `{{index .metadata.labels "app.kubernetes.io/name"}}`, that slash also got replaced, and the template did not work as expected.

This fix ensures that the BEL replacement only occurs within the text portions of the template, and the action blocks are being preserved.

Fixes #568

Patch:
diff --git a/.drone/drone.yml b/.drone/drone.yml
index 184c56f7..6e991bc3 100644
--- a/.drone/drone.yml
+++ b/.drone/drone.yml
@@ -291,4 +291,8 @@ get:
   path: infra/data/ci/docker_hub
   name: password
 
+---
+kind: signature
+hmac: 72469494de5902df2a0a375f1a38f1b064dcc2c3639d4978b86392f2b7c46f46
+
 ...
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..008074e2
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+    "go.formatTool": "gofmt"
+}
\ No newline at end of file
diff --git a/Makefile b/Makefile
index f1bfbe18..4a903d76 100644
--- a/Makefile
+++ b/Makefile
@@ -36,3 +36,5 @@ container: static
 # CI
 drone:
 	drone jsonnet --source .drone/drone.jsonnet --target .drone/drone.yml --stream --format
+	drone lint .drone/drone.yml
+	drone sign --save grafana/tanka .drone/drone.yml
diff --git a/pkg/tanka/export.go b/pkg/tanka/export.go
index 8916bd0a..69109595 100644
--- a/pkg/tanka/export.go
+++ b/pkg/tanka/export.go
@@ -175,7 +175,7 @@ func writeExportFile(path string, data []byte) error {
 
 func createTemplate(format string, env manifest.Manifest) (*template.Template, error) {
 	// Replace all os.path separators in string with BelRune for creating subfolders
-	replaceFormat := strings.Replace(format, string(os.PathSeparator), BelRune, -1)
+	replaceFormat := replaceTmplText(format, string(os.PathSeparator), BelRune)
 
 	envMap := template.FuncMap{"env": func() manifest.Manifest { return env }}
 
@@ -189,6 +189,24 @@ func createTemplate(format string, env manifest.Manifest) (*template.Template, e
 	return template, nil
 }
 
+func replaceTmplText(s, old, new string) string {
+	parts := []string{}
+	l := strings.Index(s, "{{")
+	r := strings.Index(s, "}}") + 2
+
+	for l != -1 && l < r {
+		// replace only in text between template action blocks
+		text := strings.ReplaceAll(s[:l], old, new)
+		action := s[l:r]
+		parts = append(parts, text, action)
+		s = s[r:]
+		l = strings.Index(s, "{{")
+		r = strings.Index(s, "}}") + 2
+	}
+	parts = append(parts, strings.ReplaceAll(s, old, new))
+	return strings.Join(parts, "")
+}
+
 func applyTemplate(template *template.Template, m manifest.Manifest) (path string, err error) {
 	buf := bytes.Buffer{}
 	if err := template.Execute(&buf, m); err != nil {


Test patch:
diff --git a/pkg/tanka/export_test.go b/pkg/tanka/export_test.go
new file mode 100644
index 00000000..9663c695
--- /dev/null
+++ b/pkg/tanka/export_test.go
@@ -0,0 +1,33 @@
+package tanka
+
+import "testing"
+
+func Test_replaceTmplText(t *testing.T) {
+	type args struct {
+		s   string
+		old string
+		new string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{"text only", args{"a", "a", "b"}, "b"},
+		{"action blocks", args{"{{a}}{{.}}", "a", "b"}, "{{a}}{{.}}"},
+		{"mixed", args{"a{{a}}a{{a}}a", "a", "b"}, "b{{a}}b{{a}}b"},
+		{"invalid template format handled as text", args{"a}}a{{a", "a", "b"}, "b}}b{{b"},
+		{
+			name: "keep path separator in action block",
+			args: args{`{{index .metadata.labels "app.kubernetes.io/name"}}/{{.metadata.name}}`, "/", BelRune},
+			want: "{{index .metadata.labels \"app.kubernetes.io/name\"}}\u0007{{.metadata.name}}",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := replaceTmplText(tt.args.s, tt.args.old, tt.args.new); got != tt.want {
+				t.Errorf("replaceInTmplText() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}