Skip to content

Commit

Permalink
add shallow flag to grep/replace commands (#97)
Browse files Browse the repository at this point in the history
this flag limits searches to only the immediate leaf nodes of a path rather than deeply recursing through every path below the specified path. we found this to be very useful when we have a large path structure below a parent path and want to exclude it during these operations.
  • Loading branch information
mattlqx authored Jun 15, 2021
1 parent e40a71c commit f29fe6e
Show file tree
Hide file tree
Showing 10 changed files with 45 additions and 21 deletions.
2 changes: 1 addition & 1 deletion cli/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func cmdPath(pwd string, arg string) (result string) {

func runCommandWithTraverseTwoPaths(client *client.Client, source string, target string, f func(string, string) error) {
source = filepath.Clean(source) // remove potential trailing '/'
for _, path := range client.Traverse(source) {
for _, path := range client.Traverse(source, false) {
target := strings.Replace(path, source, target, 1)
err := f(path, target)
if err != nil {
Expand Down
13 changes: 7 additions & 6 deletions cli/grep.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ type GrepCommand struct {

// GrepCommandArgs provides a struct for go-arg parsing
type GrepCommandArgs struct {
Search string `arg:"positional,required"`
Path string `arg:"positional"`
Regexp bool `arg:"-e,--regexp" help:"Treat search string as a regexp"`
Keys bool `arg:"-k,--keys" help:"Match against keys (true if -v is not specified)"`
Values bool `arg:"-v,--values" help:"Match against values (true if -k is not specified)"`
Search string `arg:"positional,required"`
Path string `arg:"positional"`
Keys bool `arg:"-k,--keys" help:"Match against keys (true if -v is not specified)"`
Regexp bool `arg:"-e,--regexp" help:"Treat search string as a regexp"`
Shallow bool `arg:"-S,--shallow" help:"Only search leaf nodes of the path rather than recurse deeper"`
Values bool `arg:"-v,--values" help:"Match against values (true if -k is not specified)"`
}

// Description provides detail on what the command does
Expand Down Expand Up @@ -92,7 +93,7 @@ func (cmd *GrepCommand) Parse(args []string) error {
// Run executes 'grep' with given GrepCommand's parameters
func (cmd *GrepCommand) Run() int {
path := cmdPath(cmd.client.Pwd, cmd.args.Path)
filePaths, err := cmd.client.SubpathsForPath(path)
filePaths, err := cmd.client.SubpathsForPath(path, cmd.args.Shallow)
if err != nil {
log.UserError(fmt.Sprintf("%s", err))
return 1
Expand Down
11 changes: 6 additions & 5 deletions cli/replace.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ type ReplaceCommandArgs struct {
Search string `arg:"positional,required"`
Replacement string `arg:"positional,required"`
Path string `arg:"positional"`
Regexp bool `arg:"-e,--regexp" help:"Treat search string and selector as a regexp"`
KeySelector string `arg:"-s,--key-selector" help:"Limit replacements to specified key" placeholder:"PATTERN"`
Keys bool `arg:"-k,--keys" help:"Match against keys (true if -v is not specified)"`
Values bool `arg:"-v,--values" help:"Match against values (true if -k is not specified)"`
Confirm bool `arg:"-y,--confirm" help:"Write results without prompt"`
DryRun bool `arg:"-n,--dry-run" help:"Skip writing results without prompt"`
KeySelector string `arg:"-s,--key-selector" help:"Limit replacements to specified key" placeholder:"PATTERN"`
Keys bool `arg:"-k,--keys" help:"Match against keys (true if -v is not specified)"`
Output MatchOutputArg `arg:"-o,--output" help:"Present changes as 'inline' with color or traditional 'diff'" default:"inline"`
Regexp bool `arg:"-e,--regexp" help:"Treat search string and selector as a regexp"`
Shallow bool `arg:"-S,--shallow" help:"Only search leaf nodes of the path rather than recurse deeper"`
Values bool `arg:"-v,--values" help:"Match against values (true if -k is not specified)"`
}

// Description provides detail on what the command does
Expand Down Expand Up @@ -113,7 +114,7 @@ func (cmd *ReplaceCommand) Parse(args []string) error {
// Run executes 'replace' with given ReplaceCommand's parameters
func (cmd *ReplaceCommand) Run() int {
path := cmdPath(cmd.client.Pwd, cmd.args.Path)
filePaths, err := cmd.client.SubpathsForPath(path)
filePaths, err := cmd.client.SubpathsForPath(path, cmd.args.Shallow)
if err != nil {
log.UserError(fmt.Sprintf("%s", err))
return 1
Expand Down
2 changes: 1 addition & 1 deletion cli/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (cmd *RemoveCommand) Run() int {
case client.LEAF:
cmd.removeSecret(newPwd)
case client.NODE:
for _, path := range cmd.client.Traverse(newPwd) {
for _, path := range cmd.client.Traverse(newPwd, false) {
err := cmd.removeSecret(path)
if err != nil {
return 1
Expand Down
11 changes: 7 additions & 4 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,23 +154,26 @@ func (client *Client) GetType(absolutePath string) (kind PathKind) {
}

// Traverse traverses given absolutePath via DFS and returns sub-paths in array
func (client *Client) Traverse(absolutePath string) (paths []string) {
func (client *Client) Traverse(absolutePath string, shallow bool) (paths []string) {
if client.isTopLevelPath(absolutePath) {
paths = client.topLevelTraverse()
} else {
paths = client.lowLevelTraverse(normalizedVaultPath(absolutePath))
paths = client.lowLevelTraverse(normalizedVaultPath(absolutePath), shallow)
}

return paths
}

// SubpathsForPath will return an array of absolute paths at or below path
func (client *Client) SubpathsForPath(path string) (filePaths []string, err error) {
func (client *Client) SubpathsForPath(path string, shallow bool) (filePaths []string, err error) {
switch t := client.GetType(path); t {
case LEAF:
filePaths = append(filePaths, filepath.Clean(path))
case NODE:
for _, traversedPath := range client.Traverse(path) {
for _, traversedPath := range client.Traverse(path, shallow) {
if shallow && client.GetType(traversedPath) == NODE {
continue
}
filePaths = append(filePaths, traversedPath)
}
default:
Expand Down
6 changes: 4 additions & 2 deletions client/traverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func (client *Client) topLevelTraverse() (result []string) {
return result
}

func (client *Client) lowLevelTraverse(path string) (result []string) {
func (client *Client) lowLevelTraverse(path string, shallow bool) (result []string) {
s, err := client.cache.List(client.getKVMetaDataPath(path))
if err != nil {
log.AppTrace("%+v", err)
Expand All @@ -27,7 +27,9 @@ func (client *Client) lowLevelTraverse(path string) (result []string) {
// prevent ambiguous dir/file to be added twice
if strings.HasSuffix(val, "/") {
// dir
result = append(result, client.lowLevelTraverse(path+"/"+val)...)
if shallow == false {
result = append(result, client.lowLevelTraverse(path+"/"+val, false)...)
}
} else {
// file
leaf := strings.ReplaceAll("/"+path+"/"+val, "//", "/")
Expand Down
5 changes: 3 additions & 2 deletions doc/commands/grep.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# grep

```text
grep [-e|--regexp] [-k|--keys] [-v|--values] SEARCH PATH
grep [-e|--regexp] [-k|--keys] [-v|--values] [-S|--shallow] SEARCH PATH
```

`grep` recursively searches the given `SEARCH` substring in key and value pairs of given `PATH`. To treat the search string as a regular-expression, add `-e` or `--regexp` to the end of the command. By default, both keys and values will be searched. If you would like to limit the search, you may add `-k` or `--keys` to the end of the command to search only a path's keys, or `-v` or `--values` to search only a path's values.
If you are looking for copies or just trying to find the path to a certain string, this command might come in handy.
If you do not desire a deep recursive search, you can use the `-S` or `--shallow` flag to limit searching to only the leaf nodes of the given path.
If you are looking for copies or just trying to find the path to a certain string, this command might come in handy.
2 changes: 2 additions & 0 deletions doc/commands/replace.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

`replace` works similarly to `grep`, but has the ability to mutate data inside Vault. By default, confirmation is required before writing data. You may skip confirmation by using the `-y`/`--confirm` flags. Conversely, you may use the `-n`/`--dry-run` flags to skip both confirmation and any writes. Changes that would be made are presented in red (delete) and green (add) coloring.

If you do not desire a deep recursive search, you can use the `-S` or `--shallow` flag to limit searching to only the leaf nodes of the given path.

This command has two output formats available via the `--output` flag:
- `inline`: A colorized inline format where deletions are in red background text and additions are in green background text. This is the default.
- `diff`: A non-colorized format that prints changes in two lines prefixed with a `-` for before and `+` for after replacement. This is more useful for copying and pasting the result.
7 changes: 7 additions & 0 deletions test/suites/commands/grep.bats
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ load ../../bin/plugins/bats-assert/load
assert_line --partial "unknown argument --foo"
assert_failure 1

#######################################
echo "==== case: shallow search"
run ${APP_BIN} -c "grep -S 'tree' /${KV_BACKEND}/src/staging/all"
assert_line --partial "/${KV_BACKEND}/src/staging/all"
refute_line --partial "/${KV_BACKEND}/src/staging/all/v1"
refute_line --partial "/${KV_BACKEND}/src/staging/all/v2"

#######################################
echo "==== case: match in pwd ===="
export VAULT_PATH=${KV_BACKEND}/src/dev
Expand Down
7 changes: 7 additions & 0 deletions test/suites/commands/replace.bats
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ load ../../bin/plugins/bats-assert/load
assert_failure
assert_line --partial "key-selector: error parsing regexp"

#######################################
echo "==== case: shallow replace"
run ${APP_BIN} -c "replace -e -S -k 'tree' '.*' 'maple' /${KV_BACKEND}/src/staging/all"
assert_line --partial "/${KV_BACKEND}/src/staging/all"
refute_line --partial "/${KV_BACKEND}/src/staging/all/v1"
refute_line --partial "/${KV_BACKEND}/src/staging/all/v2"

#######################################
echo "==== case: replace in pwd ===="
export VAULT_PATH=${KV_BACKEND}/src/a
Expand Down

0 comments on commit f29fe6e

Please sign in to comment.