diff --git a/cmd/tk/workflow.go b/cmd/tk/workflow.go index a180d6e8e..636835867 100644 --- a/cmd/tk/workflow.go +++ b/cmd/tk/workflow.go @@ -4,6 +4,8 @@ import ( "fmt" "log" "os" + "regexp" + "strings" "github.com/posener/complete" "github.com/spf13/cobra" @@ -41,7 +43,7 @@ func applyCmd() *cobra.Command { log.Fatalln("Evaluating jsonnet:", err) } - desired, err := kube.Reconcile(raw, vars.targets...) + desired, err := kube.Reconcile(raw, stringsToRegexps(vars.targets)...) if err != nil { log.Fatalln("Reconciling:", err) } @@ -84,7 +86,7 @@ func diffCmd() *cobra.Command { kube.Spec.DiffStrategy = cmd.Flag("diff-strategy").Value.String() } - desired, err := kube.Reconcile(raw, vars.targets...) + desired, err := kube.Reconcile(raw, stringsToRegexps(vars.targets)...) if err != nil { log.Fatalln("Reconciling:", err) } @@ -148,7 +150,7 @@ func showCmd() *cobra.Command { log.Fatalln("Evaluating jsonnet:", err) } - state, err := kube.Reconcile(raw, vars.targets...) + state, err := kube.Reconcile(raw, stringsToRegexps(vars.targets)...) if err != nil { log.Fatalln("Reconciling:", err) } @@ -162,3 +164,18 @@ 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) + } + return exps +} diff --git a/docs/targets.md b/docs/targets.md index deaccff98..9ae725ac4 100644 --- a/docs/targets.md +++ b/docs/targets.md @@ -24,3 +24,47 @@ multiple objects match this pattern, all of them are used. The `--target` / `-t` flag can be specified multiple times, to work with multiple objects. + + +## Regular Expressions +The argument passed to the `--target` flag is interpreted as a +[RE2](https://golang.org/s/re2syntax) regular expression. + +This allows you to use all sorts of wildcards and other advanced matching +functionality to select Kubernetes objects: + +```bash +# show all deployments +$ tk show . -t 'deployment/.*' + +# show all objects named "loki" +$ tk show . -t '.*/loki' +``` + +### Gotchas +When using regular expressions, there are some things to watch out for: + +#### Line Anchors +Tanka automatically surrounds your regular expression with line anchors: +```text +^$ +``` +For example, `--target 'deployment/.*'` becomes `^deployment/.*$`. + +#### Quoting +Regular expressions may consist of characters that have special meanings in +shell. Always make sure to properly quote your regular expression using **single +quotes**. + +```zsh +# shell attempts to match the wildcard itself: +zsh-5.4.2$ tk show . -t deployment/.* +zsh: no matches found: deployment/.* + +# properly quoted: +zsh-5.4.2$ tk show . -t 'deployment/.*' +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +# ... +``` diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index 945646988..25688f12d 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -3,6 +3,7 @@ package kubernetes import ( "bytes" "fmt" + "regexp" "sort" "strings" @@ -56,7 +57,7 @@ func (m Manifest) Namespace() string { // Reconcile receives the raw evaluated jsonnet as a marshaled json dict and // shall return it reconciled as a state object of the target system -func (k *Kubernetes) Reconcile(raw map[string]interface{}, objectspecs ...string) (state []Manifest, err error) { +func (k *Kubernetes) Reconcile(raw map[string]interface{}, objectspecs ...*regexp.Regexp) (state []Manifest, err error) { docs, err := walkJSON(raw, "") out := make([]Manifest, 0, len(docs)) if err != nil { @@ -74,7 +75,7 @@ func (k *Kubernetes) Reconcile(raw map[string]interface{}, objectspecs ...string out = funk.Filter(out, func(i interface{}) bool { p := objectspec(i.(Manifest)) for _, o := range objectspecs { - if strings.EqualFold(p, o) { + if o.MatchString(strings.ToLower(p)) { return true } }