Repository: grafana/tanka (go)

Issue:
tk init should also download the k8s libsonnet
Tanka is kubernetes focused but it's surprising that we need to download the k8s.libsonnet anyways using bundler. I would expect that `tk init` already contains the jsonnet required.

Pull request:
feat(cli): install k.libsonnet during init
Uses `jb` to install k.libsonnet and kausal.libsonnet during `tk init`.

Can be disable using `--k8s=false`

Fixes #52 

Patch:
diff --git a/cmd/tk/init.go b/cmd/tk/init.go
index 90c692a0..f623164a 100644
--- a/cmd/tk/init.go
+++ b/cmd/tk/init.go
@@ -1,10 +1,12 @@
 package main
 
 import (
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"log"
 	"os"
+	"os/exec"
 
 	"github.com/spf13/cobra"
 
@@ -18,8 +20,13 @@ func initCmd() *cobra.Command {
 		Short: "Create the directory structure",
 		Args:  cobra.NoArgs,
 	}
+
 	force := cmd.Flags().BoolP("force", "f", false, "ignore the working directory not being empty")
+	installK8sLibFlag := cmd.Flags().Bool("k8s", true, "set to false to skip installation of k.libsonnet")
+
 	cmd.Run = func(cmd *cobra.Command, args []string) {
+		failed := false
+
 		files, err := ioutil.ReadDir(".")
 		if err != nil {
 			log.Fatalln("Error listing files:", err)
@@ -45,11 +52,67 @@ func initCmd() *cobra.Command {
 			log.Fatalln(err)
 		}
 
+		if *installK8sLibFlag {
+			if err := installK8sLib(); err != nil {
+				// This is not fatal, as most of Tanka will work anyways
+				log.Println("Installing k.libsonnet:", err)
+				failed = true
+			}
+		}
+
 		fmt.Println("Directory structure set up! Remember to configure the API endpoint:\n`tk env set environments/default --server=127.0.0.1:6443`")
+		if failed {
+			log.Println("Errors occured while initializing the project. Check the above logs for details.")
+		}
 	}
 	return cmd
 }
 
+func installK8sLib() error {
+	if _, err := exec.LookPath("jb"); err != nil {
+		return errors.New("jsonnet-bundler not found in $PATH. Follow https://tanka.dev/install#jsonnet-bundler for installation instructions.")
+	}
+
+	// TODO: use the jb packages for this once refactored there
+	const klibsonnetJsonnetfile = `{
+"dependencies": [
+    {
+      "source": {
+        "git": {
+          "remote": "https://github.com/grafana/jsonnet-libs",
+          "subdir": "ksonnet-util"
+        }
+      },
+      "version": "master"
+    },
+    {
+      "source": {
+        "git": {
+          "remote": "https://github.com/ksonnet/ksonnet-lib",
+          "subdir": "ksonnet.beta.4"
+        }
+      },
+      "version": "master"
+    }
+  ]
+}
+`
+
+	if err := writeNewFile("lib/k.libsonnet", `import "k.libsonnet"`); err != nil {
+		return err
+	}
+
+	if err := ioutil.WriteFile("jsonnetfile.json", []byte(klibsonnetJsonnetfile), 0644); err != nil {
+		return err
+	}
+
+	cmd := exec.Command("jb", "install")
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+
+	return cmd.Run()
+}
+
 // writeNewFile writes the content to a file if it does not exist
 func writeNewFile(name, content string) error {
 	if _, err := os.Stat(name); os.IsNotExist(err) {
diff --git a/cmd/tk/workflow.go b/cmd/tk/workflow.go
index 67aa6273..1fab26d8 100644
--- a/cmd/tk/workflow.go
+++ b/cmd/tk/workflow.go
@@ -5,13 +5,13 @@ import (
 	"log"
 	"os"
 	"regexp"
-	"strings"
 
 	"github.com/posener/complete"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 
 	"github.com/grafana/tanka/pkg/cli/cmp"
+	"github.com/grafana/tanka/pkg/kubernetes/util"
 	"github.com/grafana/tanka/pkg/tanka"
 )
 
@@ -136,17 +136,10 @@ func showCmd() *cobra.Command {
 	return cmd
 }
 
-// stringsToRegexps compiles each string to a regular expression
-func stringsToRegexps(strs []string) (exps []*regexp.Regexp) {
-	exps = make([]*regexp.Regexp, 0, len(strs))
-	for _, raw := range strs {
-		// surround the regular expression with start and end markers
-		s := fmt.Sprintf(`^%s$`, raw)
-		exp, err := regexp.Compile(s)
-		if err != nil {
-			log.Fatalf("%s.\nSee https://tanka.dev/targets/#regular-expressions for details on regular expressions.", strings.Title(err.Error()))
-		}
-		exps = append(exps, exp)
+func stringsToRegexps(exps []string) []*regexp.Regexp {
+	regexs, err := util.CompileTargetExps(exps)
+	if err != nil {
+		log.Fatalln(err)
 	}
-	return exps
+	return regexs
 }
diff --git a/pkg/kubernetes/reconcile.go b/pkg/kubernetes/reconcile.go
index 7babb897..78d1a8cc 100644
--- a/pkg/kubernetes/reconcile.go
+++ b/pkg/kubernetes/reconcile.go
@@ -39,7 +39,7 @@ func Reconcile(raw map[string]interface{}, spec v1alpha1.Spec, targets []*regexp
 		tmp := funk.Filter(out, func(i interface{}) bool {
 			p := objectspec(i.(manifest.Manifest))
 			for _, t := range targets {
-				if t.MatchString(strings.ToLower(p)) {
+				if t.MatchString(p) {
 					return true
 				}
 			}
diff --git a/pkg/kubernetes/util/targetexp.go b/pkg/kubernetes/util/targetexp.go
new file mode 100644
index 00000000..79f1d5c9
--- /dev/null
+++ b/pkg/kubernetes/util/targetexp.go
@@ -0,0 +1,40 @@
+package util
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+)
+
+type ErrBadTargetExp struct {
+	inner error
+}
+
+func (e ErrBadTargetExp) Error() string {
+	return fmt.Sprintf("%s.\nSee https://tanka.dev/targets/#regular-expressions for details on regular expressions.", strings.Title(e.inner.Error()))
+}
+
+// CompileTargetExps compiles the regular expression for each target
+func CompileTargetExps(strs []string) (exps []*regexp.Regexp, err error) {
+	exps = make([]*regexp.Regexp, 0, len(strs))
+	for _, raw := range strs {
+		// surround the regular expression with start and end markers
+		s := fmt.Sprintf(`(?i)^%s$`, raw)
+		exp, err := regexp.Compile(s)
+		if err != nil {
+			return nil, ErrBadTargetExp{err}
+		}
+		exps = append(exps, exp)
+	}
+	return exps, nil
+}
+
+// MustCompileTargetExps is like CompileTargetExps but panics on error
+// Meant for static code
+func MustCompileTargetExps(strs ...string) (exps []*regexp.Regexp) {
+	exps, err := CompileTargetExps(strs)
+	if err != nil {
+		panic(err)
+	}
+	return exps
+}


Test patch:
diff --git a/pkg/kubernetes/kubernetes_test.go b/pkg/kubernetes/kubernetes_test.go
index 0e621d16..09ae31b2 100644
--- a/pkg/kubernetes/kubernetes_test.go
+++ b/pkg/kubernetes/kubernetes_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/grafana/tanka/pkg/kubernetes/manifest"
+	"github.com/grafana/tanka/pkg/kubernetes/util"
 	"github.com/grafana/tanka/pkg/spec/v1alpha1"
 )
 
@@ -35,10 +36,10 @@ func TestReconcile(t *testing.T) {
 				testDataDeep().flat[".app.web.backend.server.nginx.deployment"],
 				testDataDeep().flat[".app.web.frontend.nodejs.express.service"],
 			},
-			targets: []*regexp.Regexp{
-				regexp.MustCompile("deployment/nginx"),
-				regexp.MustCompile("service/frontend"),
-			},
+			targets: util.MustCompileTargetExps(
+				`deployment/nginx`,
+				`service/frontend`,
+			),
 		},
 		{
 			name: "targets-regex",
@@ -47,7 +48,17 @@ func TestReconcile(t *testing.T) {
 				testDataDeep().flat[".app.web.backend.server.nginx.deployment"],
 				testDataDeep().flat[".app.web.frontend.nodejs.express.deployment"],
 			},
-			targets: []*regexp.Regexp{regexp.MustCompile("deployment/.*")},
+			targets: util.MustCompileTargetExps(`deployment/.*`),
+		},
+		{
+			name: "targets-caseInsensitive",
+			deep: testDataDeep().deep,
+			flat: manifest.List{
+				testDataDeep().flat[".app.web.backend.server.nginx.deployment"],
+			},
+			targets: util.MustCompileTargetExps(
+				`DePlOyMeNt/NgInX`,
+			),
 		},
 		{
 			name: "force-namespace",