Skip to content

Commit

Permalink
fix kv2 engine enabling and introduce printer package
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Morelly committed Dec 14, 2021
1 parent 874ec30 commit 5977bdd
Show file tree
Hide file tree
Showing 17 changed files with 1,836 additions and 582 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ jobs:
uses: golangci/golangci-lint-action@v2
with:
version: v1.43.0
args: -c .golang-ci.yml
args: -c .golang-ci.yml
env:
GO111MODULES: off
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ jobs:
fetch-depth: 2
- uses: actions/setup-go@v2
with:
go-version: '1.16'
go-version: '1.17'

- name: go get
run: go get ./...

- name: go mod tidy
run: go mod tidy

- name: Run coverage
run: go test -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Upload coverage to Codecov
Expand Down
14 changes: 2 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
vkv
/.idea
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
vendor/

coverage.out

# goreleaser
/dist

.envrc
.envrc
format.yaml
3 changes: 2 additions & 1 deletion .golang-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ linters:
- goerr113
- wrapcheck
- gochecknoglobals
- varnamelen
- varnamelen
- funlen
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Flags:
-h, --help help for vkv
--only-keys print only keys
--only-paths print only paths
-p, --root-path string root path (default "kv2")
-p, --root-path string root path (default "kv")
--show-secrets print out secrets
-s, --sub-path string sub path
-j, --to-json print secrets in json format
Expand All @@ -50,7 +50,7 @@ secret/sub/demo
secret/sub/sub2/demo
```

## list secrets `--root-paths | -p (default kv2)`
## list secrets `--root-path | -p (default kv)`
You can list all secrets recursively by running:

```bash
Expand Down Expand Up @@ -148,4 +148,11 @@ secret/sub/sub2/demo:
foo: bar
password: password
user: user
```
```
# Acknowledgements / Similar tools
`vkv` is inspired by:
* https://github.com/jonasvinther/medusa

Similar tools are:
* https://github.com/kir4h/rvault
220 changes: 14 additions & 206 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,19 @@
package cmd

import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"sort"
"strings"
"text/tabwriter"

"github.com/FalcoSuessgott/vkv/pkg/printer"
"github.com/FalcoSuessgott/vkv/pkg/vault"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
)

const (
delimiter = "/"
separator = " "
newLine = "\n"
maskChar = "*"
defaultKVPath = "kv2"
)
const defaultKVPath = "kv"

var defaultWriter = os.Stdout

type (
secrets map[string]interface{}
keys []string
)

// Options holds all available commandline options.
type Options struct {
rootPath string
Expand All @@ -51,10 +35,8 @@ func defaultOptions() *Options {
}
}

//nolint: funlen
func newRootCmd(version string) *cobra.Command {
o := defaultOptions()
s := secrets{}

cmd := &cobra.Command{
Use: "vkv",
Expand All @@ -67,32 +49,30 @@ func newRootCmd(version string) *cobra.Command {

return nil
}

if err := o.validateFlags(); err != nil {
return err
}

// READ
v, err := vault.NewClient()
if err != nil {
return err
}

s.listRecursive(v, o.rootPath, o.subPath)

// MODIFY
o.evalModifyFlags(s)

// PRINT
if !o.json && !o.yaml {
o.print(s)
if err := v.ListRecursive(o.rootPath, o.subPath); err != nil {
return err
}

if o.json {
fmt.Println(s.toJSON())
}
printer := printer.NewPrinter(v.Secrets,
printer.OnlyKeys(o.onlyKeys),
printer.OnlyPaths(o.onlyPaths),
printer.ShowSecrets(o.showSecrets),
printer.ToJSON(o.json),
printer.ToYAML(o.yaml),
)

if o.yaml {
fmt.Println(s.toYAML())
if err := printer.Out(); err != nil {
return err
}

return nil
Expand Down Expand Up @@ -145,175 +125,3 @@ func (o *Options) validateFlags() error {

return nil
}

func (o *Options) evalModifyFlags(s secrets) {
if o.onlyKeys {
s.onlykeys()
}

if o.onlyPaths {
s.onlyPaths()
}

if !o.showSecrets {
s.maskSecrets()
}
}

func (s *secrets) listRecursive(v *vault.Vault, rootPath, subPath string) {
keys, err := v.ListPath(rootPath, subPath)
if err != nil {
log.Fatalf("error listing secrets at %s/%s: %v.\n", rootPath, subPath, err)
}

for _, k := range keys {
if strings.HasSuffix(k, delimiter) {
s.listRecursive(v, rootPath, buildPath(subPath, k))
} else {
secrets, err := v.ReadSecrets(rootPath, buildPath(subPath, k))
if err != nil {
log.Fatalf("error reading secret at %s/%s/%s: %v.\n", rootPath, subPath, k, err)
}

(*s)[buildPath(rootPath, subPath, k)] = secrets
}
}
}

func (s secrets) toJSON() string {
out, err := json.Marshal(s)
if err != nil {
log.Fatalf("error while marshalling map: %v\n", err)
}

return string(out)
}

func (s *secrets) toYAML() string {
out, err := yaml.JSONToYAML([]byte(s.toJSON()))
if err != nil {
log.Fatalf("error while marshalling from json: %v\n", err)
}

return string(out)
}

func sortMapKeys(m map[string]interface{}) []string {
keys := make(keys, 0, len(m))
for k := range m {
keys = append(keys, k)
}

sort.Sort(keys)

return keys
}

func (k keys) Len() int {
return len(k)
}

func (k keys) Swap(i, j int) {
k[i], k[j] = k[j], k[i]
}

func (k keys) Less(i, j int) bool {
k1 := strings.ReplaceAll(k[i], "/", "\x00")
k2 := strings.ReplaceAll(k[j], "/", "\x00")

return k1 < k2
}

func (o *Options) print(s secrets) {
//nolint: gomnd
w := tabwriter.NewWriter(o.writer, 0, 8, 1, '\t', tabwriter.AlignRight)

fmt.Fprintf(w, "%s%s%s", buildPath(o.rootPath, o.subPath), delimiter, newLine)

for _, k := range sortMapKeys(s) {
// nolint
if o.onlyKeys {
fmt.Fprintf(w, "%s\t%v\n", k, printMap(s[k]))
} else if o.onlyPaths {
fmt.Fprintln(w, k)
} else {
fmt.Fprintf(w, "%s\t%v\n", k, printMap(s[k]))
}
}

w.Flush()
}

func (s secrets) onlykeys() {
for k := range s {
m, ok := s[k].(map[string]interface{})
if !ok {
continue
}

for k := range m {
m[k] = ""
}
}
}

func (s secrets) onlyPaths() {
for k := range s {
s[k] = nil
}
}

func (s secrets) maskSecrets() {
for k := range s {
m, ok := s[k].(map[string]interface{})
if !ok {
continue
}

for k := range m {
secret := fmt.Sprintf("%v", m[k])
m[k] = strings.Repeat(maskChar, len(secret))
}
}
}

func buildPath(elements ...string) string {
p := ""

for i, e := range elements {
e = strings.TrimSuffix(e, delimiter)

if e == "" {
continue
}

p += e

if i < len(elements) {
p += delimiter
}
}

return strings.TrimSuffix(p, delimiter)
}

func printMap(m interface{}) string {
out := ""

secrets, ok := m.(map[string]interface{})
if !ok {
return ""
}

for _, k := range sortMapKeys(secrets) {
out += k

if secrets[k] == "" {
out += separator
} else {
out += fmt.Sprintf("=%v ", secrets[k])
}
}

return strings.TrimSuffix(out, separator)
}
Loading

0 comments on commit 5977bdd

Please sign in to comment.