Skip to content

Commit

Permalink
feat(cli): Completions (grafana#7)
Browse files Browse the repository at this point in the history
Adds CLI-completion support to tanka 🎉 

The main workflow commands (show, diff, apply) now support completing the environment.
This is done using a combination of spf13/cobra and posener/complete.

Supported shells are bash, zsh and fish. Install using `tk --install-completion`
  • Loading branch information
sh0rez committed Aug 6, 2019
1 parent 55adf80 commit aea3bdf
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 157 deletions.
3 changes: 3 additions & 0 deletions cmd/tk/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ func jpathCmd() *cobra.Command {
Short: "print information about the jpath",
Use: "jpath [directory]",
Args: cobra.ExactArgs(1),
Annotations: map[string]string{
"args": "baseDir",
},
Run: func(cmd *cobra.Command, args []string) {
pwd, err := filepath.Abs(args[0])
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions cmd/tk/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func initCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Create the directory structure",
Args: cobra.NoArgs,
}
force := cmd.Flags().BoolP("force", "f", false, "ignore the working directory not being empty")
cmd.Run = func(cmd *cobra.Command, args []string) {
Expand Down
3 changes: 3 additions & 0 deletions cmd/tk/jsonnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ func evalCmd() *cobra.Command {
Short: "evaluate the jsonnet to json",
Use: "eval [directory]",
Args: cobra.ExactArgs(1),
Annotations: map[string]string{
"args": "baseDir",
},
}

cmd.Run = func(cmd *cobra.Command, args []string) {
Expand Down
91 changes: 60 additions & 31 deletions cmd/tk/main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package main

import (
"flag"
"log"
"path/filepath"

"github.com/posener/complete"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/sh0rez/tanka/pkg/cmp"
"github.com/sh0rez/tanka/pkg/config/v1alpha1"
"github.com/sh0rez/tanka/pkg/kubernetes"
)
Expand Down Expand Up @@ -34,41 +37,15 @@ func main() {
Short: "tanka <3 jsonnet",
Version: Version,
TraverseChildren: true,
// Configuration
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// Configuration parsing. Note this is using return to abort actions
viper.SetConfigName("spec")

// no args = no command that has a baseDir passed, abort
if len(args) == 0 {
return
}

// if the first arg is not a dir, abort
pwd, err := filepath.Abs(args[0])
if err != nil {
config = setupConfiguration(args[0])
if config == nil {
return
}
viper.AddConfigPath(pwd)

// handle deprecated ksonnet spec
for old, new := range deprecated {
viper.RegisterAlias(new, old)
}

// read it
if err := viper.ReadInConfig(); err != nil {
// just run fine without config. Provider features won't work (apply, show, diff)
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
return
}

log.Fatalln(err)
}
checkDeprecated()

if err := viper.Unmarshal(&config); err != nil {
log.Fatalln(err)
}

// Kubernetes
kube = kubernetes.New(config.Spec)
Expand All @@ -94,14 +71,66 @@ func main() {
debugCmd(),
)

// other commands
rootCmd.AddCommand(completionCommand(rootCmd))
// completion
cmp.Handlers.Add("baseDir", complete.PredictFunc(
func(complete.Args) []string {
return findBaseDirs()
},
))

c := complete.New("tk", cmp.Create(rootCmd))
c.InstallName = "install-completion"
c.UninstallName = "uninstall-completion"
fs := &flag.FlagSet{}
c.AddFlags(fs)
rootCmd.Flags().AddGoFlagSet(fs)

rootCmd.Run = func(cmd *cobra.Command, args []string) {
if c.Complete() {
return
}
_ = cmd.Help()
}

// Run!
if err := rootCmd.Execute(); err != nil {
log.Fatalln("Ouch:", rootCmd.Execute())
}
}

func setupConfiguration(baseDir string) *v1alpha1.Config {
viper.SetConfigName("spec")

// if the baseDir arg is not a dir, abort
pwd, err := filepath.Abs(baseDir)
if err != nil {
return nil
}
viper.AddConfigPath(pwd)

// handle deprecated ksonnet spec
for old, new := range deprecated {
viper.RegisterAlias(new, old)
}

// read it
if err := viper.ReadInConfig(); err != nil {
// just run fine without config. Provider features won't work (apply, show, diff)
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
return nil
}

log.Fatalln(err)
}
checkDeprecated()

var config v1alpha1.Config
if err := viper.Unmarshal(&config); err != nil {
log.Fatalln(err)
}
return &config
}

func checkDeprecated() {
for old, use := range deprecated {
if viper.IsSet(old) {
Expand Down
44 changes: 22 additions & 22 deletions cmd/tk/other.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
package main

import (
"fmt"
"log"
"os"
"strings"
"path/filepath"

"github.com/spf13/cobra"
"github.com/sh0rez/tanka/pkg/jpath"
)

func completionCommand(rootCmd *cobra.Command) *cobra.Command {
return &cobra.Command{
Use: "completion bash|zsh",
Example: `
eval "$(tk completion bash)"
eval "$(tk completion zsh)"
`,
Short: `create bash/zsh auto-completion.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
switch shell := strings.ToLower(args[0]); shell {
case "bash":
return rootCmd.GenBashCompletion(os.Stdout)
case "zsh":
return rootCmd.GenZshCompletion(os.Stdout)
default:
return fmt.Errorf("unknown shell %q. Only bash and zsh are supported", shell)
}
},
// findBaseDirs searches for possible environments
func findBaseDirs() (dirs []string) {
pwd, err := os.Getwd()
if err != nil {
return
}
_, _, _, err = jpath.Resolve(pwd)
if err == jpath.ErrorNoRoot {
return
}

if err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if _, err := os.Stat(filepath.Join(path, "main.jsonnet")); err == nil {
dirs = append(dirs, path)
}
return nil
}); err != nil {
log.Fatalln(err)
}
return dirs
}
9 changes: 9 additions & 0 deletions cmd/tk/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ func applyCmd() *cobra.Command {
Use: "apply [directory]",
Short: "apply the configuration to the cluster",
Args: cobra.ExactArgs(1),
Annotations: map[string]string{
"args": "baseDir",
},
}
cmd.Run = func(cmd *cobra.Command, args []string) {
raw, err := evalDict(args[0])
Expand All @@ -39,6 +42,9 @@ func diffCmd() *cobra.Command {
Use: "diff [directory]",
Short: "differences between the configuration and the cluster",
Args: cobra.ExactArgs(1),
Annotations: map[string]string{
"args": "baseDir",
},
}
cmd.Run = func(cmd *cobra.Command, args []string) {
raw, err := evalDict(args[0])
Expand Down Expand Up @@ -72,6 +78,9 @@ func showCmd() *cobra.Command {
Use: "show [directory]",
Short: "jsonnet as yaml",
Args: cobra.ExactArgs(1),
Annotations: map[string]string{
"args": "baseDir",
},
}
cmd.Run = func(cmd *cobra.Command, args []string) {
raw, err := evalDict(args[0])
Expand Down
8 changes: 6 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ require (
github.com/alecthomas/chroma v0.6.6
github.com/fatih/color v1.7.0
github.com/google/go-jsonnet v0.13.0
github.com/kr/pretty v0.1.0 // indirect
github.com/mitchellh/mapstructure v1.1.2
github.com/pkg/errors v0.8.1
github.com/posener/complete v1.2.1
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.4.0
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.2
github.com/stretchr/objx v0.2.0
github.com/thoas/go-funk v0.4.0
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect
golang.org/x/text v0.3.2 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.2
)
Loading

0 comments on commit aea3bdf

Please sign in to comment.