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

add an add command #87

Merged
merged 1 commit into from
Feb 23, 2021
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ENHANCEMENTS:

* Add `--output` flag to `replace` command to output as line diffs for each replacement in addition to the default inline format. ([#88](https://github.com/fishi0x01/vsh/pull/88))
* Add `add` command for single key insertion ([#87](https://github.com/fishi0x01/vsh/pull/87))

BUG FIXES:

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Download latest static binaries from [release page](https://github.com/fishi0x01

## Supported commands

- [add](doc/commands/add.md) adds a single key and value to a path
- [append](doc/commands/append.md) merges secrets with different strategies (allows recursive operation on paths)
- [cat](doc/commands/cat.md) shows the key/value pairs of a path
- [cd](doc/commands/cd.md) allows interactive navigation through the paths
Expand Down
101 changes: 101 additions & 0 deletions cli/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package cli

import (
"fmt"

"github.com/fishi0x01/vsh/client"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeClimate complains that this import is redundant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not sure what it's on about there. It doesn't compile without it as client is used in the file.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting 🤔
Once you merge #88 and rebase, it will evaluate again. Lets see what it says then. If it fails again, I will take a look

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Still failing. I will have a look 👍

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im not exactly sure what the issue here is. It seems to me that codeclimate uses a govet wrapper https://github.com/codeclimate-community/codeclimate-govet

Might be this is a bug. Running go vet . locally doesn't show any errors. I've marked the issue in CodeClimate as invalid

"github.com/fishi0x01/vsh/log"
)

// AddCommand container for all 'append' parameters
type AddCommand struct {
name string
args *AddCommandArgs

client *client.Client
}

// AddCommandArgs provides a struct for go-arg parsing
type AddCommandArgs struct {
Key string `arg:"positional,required"`
Value string `arg:"positional,required"`
Path string `arg:"positional,required"`
Force bool `arg:"-f,--force" help:"Overwrite key if exists"`
}

// Description provides detail on what the command does
func (AddCommandArgs) Description() string {
return "adds a key with value to a path"
}

// NewAddCommand creates a new AddCommand parameter container
func NewAddCommand(c *client.Client) *AddCommand {
return &AddCommand{
name: "add",
client: c,
args: &AddCommandArgs{},
}
}

// GetName returns the AddCommand's name identifier
func (cmd *AddCommand) GetName() string {
return cmd.name
}

// GetArgs provides the struct holding arguments for the command
func (cmd *AddCommand) GetArgs() interface{} {
return cmd.args
}

// IsSane returns true if command is sane
func (cmd *AddCommand) IsSane() bool {
return cmd.args.Key != "" && cmd.args.Value != "" && cmd.args.Path != ""
}

// PrintUsage print command usage
func (cmd *AddCommand) PrintUsage() {
fmt.Println(Help(cmd))
}

// Parse parses the arguments into the Command and Args structs
func (cmd *AddCommand) Parse(args []string) error {
_, err := parseCommandArgs(args, cmd)
if err != nil {
return err
}

return nil
}

// Run executes 'add' with given AddCommand's parameters
func (cmd *AddCommand) Run() int {
path := cmdPath(cmd.client.Pwd, cmd.args.Path)

pathType := cmd.client.GetType(path)
if pathType != client.LEAF {
log.UserError("Not a valid path for operation: %s", path)
return 1
}

err := cmd.addKeyValue(cmd.args.Path, cmd.args.Key, cmd.args.Value)
if err != nil {
log.UserError("Add failed: " + err.Error())
return 1
}

return 0
}

func (cmd *AddCommand) addKeyValue(path string, key string, value string) error {
secret, err := cmd.client.Read(path)
if err != nil {
return fmt.Errorf("Read failed: %s", err)
}
data := secret.GetData()
if _, ok := data[key]; ok && !cmd.args.Force {
return fmt.Errorf("Key already exists at path: %s", path)
}
data[key] = value
secret.SetData(data)
return cmd.client.Write(path, secret)
}
13 changes: 13 additions & 0 deletions cli/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"path/filepath"
"strings"

"github.com/fatih/structs"
"github.com/fishi0x01/vsh/client"
"github.com/fishi0x01/vsh/log"
)
Expand All @@ -20,6 +21,7 @@ type Command interface {

// Commands contains all available commands
type Commands struct {
Add *AddCommand
Append *AppendCommand
Cat *CatCommand
Cd *CdCommand
Expand All @@ -31,9 +33,20 @@ type Commands struct {
Rm *RemoveCommand
}

// Get returns the Command that matches the string
func (cmds *Commands) Get(cmd string) Command {
for _, f := range structs.Fields(cmds) {
if c := f.Value().(Command); cmd == c.GetName() {
return c
}
}
return nil
}

// NewCommands returns a Commands struct with all available commands
func NewCommands(client *client.Client) *Commands {
return &Commands{
Add: NewAddCommand(client),
Append: NewAppendCommand(client),
Cat: NewCatCommand(client),
Cd: NewCdCommand(client),
Expand Down
7 changes: 5 additions & 2 deletions client/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,11 @@ func transformToKV2Secret(secret api.Secret) *api.Secret {
}

func normalizedVaultPath(absolutePath string) string {
// remove trailing '/'
return absolutePath[1:]
// remove leading '/'
if absolutePath[0] == '/' {
return absolutePath[1:]
}
return absolutePath
}

func sliceContains(arr []string, search string) bool {
Expand Down
23 changes: 23 additions & 0 deletions doc/commands/add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# add

```text
add [-f|--force] KEY VALUE PATH
```

Add operation adds or overwrites a single key at path.

By default, it will add the key if it does not already exist. To overwrite the key if it exists, use the `--force` flag.

```bash
> cat secret/path

value = 1
other = thing

> add fizz buzz secret/path
> cat /secret/to

value = 1
fizz = buzz
other = thing
```
44 changes: 11 additions & 33 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,16 @@ func executor(in string) {
os.Exit(0)
default:
cmd, err = getCommand(args[0], commands)
if err == nil {
err = cmd.Parse(args[0])
}
}

if err != nil && cmd != nil {
if err != nil {
log.UserError("%v", err)
cmd.PrintUsage()
if cmd != nil {
cmd.PrintUsage()
}
}

if err != nil && !isInteractive {
Expand All @@ -94,38 +99,11 @@ func executor(in string) {
}

func getCommand(args []string, commands *cli.Commands) (cmd cli.Command, err error) {
switch args[0] {
case commands.Ls.GetName():
err = commands.Ls.Parse(args)
cmd = commands.Ls
case commands.Cd.GetName():
err = commands.Cd.Parse(args)
cmd = commands.Cd
case commands.Mv.GetName():
err = commands.Mv.Parse(args)
cmd = commands.Mv
case commands.Append.GetName():
err = commands.Append.Parse(args)
cmd = commands.Append
case commands.Cp.GetName():
err = commands.Cp.Parse(args)
cmd = commands.Cp
case commands.Rm.GetName():
err = commands.Rm.Parse(args)
cmd = commands.Rm
case commands.Cat.GetName():
err = commands.Cat.Parse(args)
cmd = commands.Cat
case commands.Grep.GetName():
err = commands.Grep.Parse(args)
cmd = commands.Grep
case commands.Replace.GetName():
err = commands.Replace.Parse(args)
cmd = commands.Replace
default:
log.UserError("Not a valid command: %s", args[0])
return nil, fmt.Errorf("not a valid command")
cmd = commands.Get(args[0])
if cmd == nil {
return nil, fmt.Errorf("not a valid command: %s", args[0])
}

return cmd, err
}

Expand Down
37 changes: 37 additions & 0 deletions test/suites/commands/add.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
load ../../util/common
load ../../util/standard-setup
load ../../bin/plugins/bats-support/load
load ../../bin/plugins/bats-assert/load

@test "vault-${VAULT_VERSION} ${KV_BACKEND} 'add'" {
#######################################
echo "==== case: add value to non existing path ===="
run ${APP_BIN} -c "add test value ${KV_BACKEND}/fake/path"
assert_failure

#######################################
echo "==== case: add key to existing path ===="
run ${APP_BIN} -c "add test value ${KV_BACKEND}/src/a/foo"
assert_success

echo "ensure the key was written to destination"
run get_vault_value "test" "${KV_BACKEND}/src/a/foo"
assert_success
assert_output "value"

#######################################
echo "==== case: add existing key to existing path ===="
run ${APP_BIN} -c "add value another ${KV_BACKEND}/src/a/foo"
assert_failure
assert_output --partial "Key already exists at path: ${KV_BACKEND}/src/a/foo"

#######################################
echo "==== case: overwrite existing key to existing path ===="
run ${APP_BIN} -c "add -f value another ${KV_BACKEND}/src/a/foo"
assert_success

echo "ensure the key was written to destination"
run get_vault_value "value" "${KV_BACKEND}/src/a/foo"
assert_success
assert_output "another"
}