Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(kubernetes): regexp targets #64

Merged
merged 1 commit into from
Aug 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions cmd/tk/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"log"
"os"
"regexp"
"strings"

"github.com/posener/complete"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
}
44 changes: 44 additions & 0 deletions docs/targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
^<your expression>$
```
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
# ...
```
5 changes: 3 additions & 2 deletions pkg/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kubernetes
import (
"bytes"
"fmt"
"regexp"
"sort"
"strings"

Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
}
Expand Down