Skip to content
Open
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
4 changes: 3 additions & 1 deletion cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
func NewGenerateCommand() *cobra.Command {
const StdIn = "-"
var configPath, secretName string
var disableRecursive bool
var verboseOutput bool
var disableCache bool

Expand All @@ -43,7 +44,7 @@ func NewGenerateCommand() *cobra.Command {
return err
}
} else {
files, err := listFiles(path)
files, err := listFiles(path, disableRecursive)
if len(files) < 1 {
return fmt.Errorf("no YAML or JSON files were found in %s", path)
}
Expand Down Expand Up @@ -119,5 +120,6 @@ func NewGenerateCommand() *cobra.Command {
command.Flags().StringVarP(&secretName, "secret-name", "s", "", "name of a Kubernetes Secret in the argocd namespace containing Vault configuration data in the argocd namespace of your ArgoCD host (Only available when used in ArgoCD). The namespace can be overridden by using the format <namespace>:<name>")
command.Flags().BoolVar(&verboseOutput, "verbose-sensitive-output", false, "enable verbose mode for detailed info to help with debugging. Includes sensitive data (credentials), logged to stderr")
command.Flags().BoolVar(&disableCache, "disable-token-cache", false, "disable the automatic token cache feature that store tokens locally")
command.Flags().BoolVar(&disableRecursive, "disable-recursive", false, "disable resursive path traversal")
return command
}
65 changes: 65 additions & 0 deletions cmd/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,71 @@ func TestMain(t *testing.T) {
}
})

t.Run("will not recurse down directories if disabled", func(t *testing.T) {
args := []string{
"--disable-recursive",
"../fixtures/input/nonempty-recursive",
}
cmd := NewGenerateCommand()

b := bytes.NewBufferString("")
e := bytes.NewBufferString("")
cmd.SetArgs(args)
cmd.SetOut(b)
cmd.SetErr(e)
cmd.Execute()
out, err := io.ReadAll(b) // Read buffer to bytes
if err != nil {
t.Fatal(err)
}
stderr, err := io.ReadAll(e) // Read buffer to bytes
if err != nil {
t.Fatal(err)
}

buf, err := os.ReadFile("../fixtures/output/secret-recursive-disabled.yaml")
if err != nil {
t.Fatal(err)
}

expected := string(buf)
if string(out) != expected {
t.Fatalf("expected:\n\n%s\nbut got:\n\n%s\nerr: %s", expected, string(out), string(stderr))
}
})

t.Run("will recurse down directories if unset", func(t *testing.T) {
args := []string{
"../fixtures/input/nonempty-recursive",
}
cmd := NewGenerateCommand()

b := bytes.NewBufferString("")
e := bytes.NewBufferString("")
cmd.SetArgs(args)
cmd.SetOut(b)
cmd.SetErr(e)
cmd.Execute()
out, err := io.ReadAll(b) // Read buffer to bytes
if err != nil {
t.Fatal(err)
}
stderr, err := io.ReadAll(e) // Read buffer to bytes
if err != nil {
t.Fatal(err)
}

buf, err := os.ReadFile("../fixtures/output/secret-recursive-enabled.yaml")
if err != nil {
t.Fatal(err)
}

expected := string(buf)
if string(out) != expected {
t.Fatalf("expected:\n\n%s\nbut got:\n\n%s\nerr: %s", expected, string(out), string(stderr))
}
})

os.Unsetenv("AVP_TYPE")
os.Unsetenv("VAULT_ADDR")
os.Unsetenv("AVP_AUTH_TYPE")
Expand Down
17 changes: 14 additions & 3 deletions cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,26 @@ import (
k8yaml "k8s.io/apimachinery/pkg/util/yaml"
)

func listFiles(root string) ([]string, error) {
func listFiles(root string, disableRecursive bool) ([]string, error) {
var files []string

err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error accessing path %s: %w", path, err)
}
if info.IsDir() {
if disableRecursive && path != root {
return filepath.SkipDir
}
return nil
}
if filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" || filepath.Ext(path) == ".json" {
files = append(files, path)
}
return nil
})
}

err := filepath.Walk(root, walkFn)
if err != nil {
return files, err
}
Expand Down
93 changes: 93 additions & 0 deletions cmd/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package cmd

import (
"os"
"path/filepath"
"reflect"
"sort"
"testing"
)

// Helper to create files and directories for testing
func createTestFiles(t *testing.T, root string, files map[string]string) {
for path, content := range files {
fullPath := filepath.Join(root, path)
dir := filepath.Dir(fullPath)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatalf("failed to create dir %s: %v", dir, err)
}
if err := os.WriteFile(fullPath, []byte(content), 0o644); err != nil {
t.Fatalf("failed to write file %s: %v", fullPath, err)
}
}
}

func TestListFiles_NonRecursive(t *testing.T) {
tmpDir := t.TempDir()
files := map[string]string{
"file1.yaml": "a: b",
"file2.yml": "c: d",
"file3.json": "{}",
"file4.txt": "not yaml",
"subdir/file5.yaml": "e: f",
}
createTestFiles(t, tmpDir, files)

got, err := listFiles(tmpDir, true)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

want := []string{
filepath.Join(tmpDir, "file1.yaml"),
filepath.Join(tmpDir, "file2.yml"),
filepath.Join(tmpDir, "file3.json"),
}
sort.Strings(got)
sort.Strings(want)
if !reflect.DeepEqual(got, want) {
t.Errorf("expected %v, got %v", want, got)
}
}

func TestListFiles_Recursive(t *testing.T) {
tmpDir := t.TempDir()
files := map[string]string{
"file1.yaml": "a: b",
"file2.yml": "c: d",
"file3.json": "{}",
"file4.txt": "not yaml",
"subdir/file5.yaml": "e: f",
"subdir/file6.json": "{}",
"subdir2/file7.yml": "g: h",
"subdir2/file8.txt": "not yaml",
}
createTestFiles(t, tmpDir, files)

got, err := listFiles(tmpDir, false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

want := []string{
filepath.Join(tmpDir, "file1.yaml"),
filepath.Join(tmpDir, "file2.yml"),
filepath.Join(tmpDir, "file3.json"),
filepath.Join(tmpDir, "subdir", "file5.yaml"),
filepath.Join(tmpDir, "subdir", "file6.json"),
filepath.Join(tmpDir, "subdir2", "file7.yml"),
}
sort.Strings(got)
sort.Strings(want)
if !reflect.DeepEqual(got, want) {
t.Errorf("expected %v, got %v", want, got)
}
}

func TestListFiles_Error(t *testing.T) {
// Non-existent directory should return an error
_, err := listFiles("/non/existent/dir", false)
if err == nil {
t.Error("expected error for non-existent directory, got nil")
}
}
2 changes: 2 additions & 0 deletions docs/cmd/generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ argocd-vault-plugin generate PATH [flags]
### Options
```
-c, --config-path string path to a file containing Vault configuration (YAML, JSON, envfile) to use
--disable-recursive disable resursive path traversal
--disable-token-cache disable the automatic token cache feature that store tokens locally
-h, --help help for generate
-s, --secret-name string name of a Kubernetes Secret in the argocd namespace containing Vault configuration data in the argocd namespace of your ArgoCD host (Only available when used in ArgoCD). The namespace can be overridden by using the format <namespace>:<name>
--verbose-sensitive-output enable verbose mode for detailed info to help with debugging. Includes sensitive data (credentials), logged to stderr
Expand Down
13 changes: 13 additions & 0 deletions fixtures/output/secret-recursive-disabled.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
data:
SECRET_NUM: MQ==
SECRET_VAR: dGVzdC1wYXNzd29yZA==
kind: Secret
metadata:
annotations:
avp.kubernetes.io/kv-version: "1"
avp.kubernetes.io/path: secret/testing
name: test-name
namespace: test-namespace
type: Opaque
---
26 changes: 26 additions & 0 deletions fixtures/output/secret-recursive-enabled.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: v1
data:
SECRET_NUM: MQ==
SECRET_VAR: dGVzdC1wYXNzd29yZA==
kind: Secret
metadata:
annotations:
avp.kubernetes.io/kv-version: "1"
avp.kubernetes.io/path: secret/testing
name: test-name
namespace: test-namespace
type: Opaque
---
apiVersion: v1
data:
SECRET_NUM: MQ==
SECRET_VAR: dGVzdC1wYXNzd29yZA==
kind: Secret
metadata:
annotations:
avp.kubernetes.io/kv-version: "1"
avp.kubernetes.io/path: secret/testing
name: test-name
namespace: test-namespace
type: Opaque
---