Skip to content
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
14 changes: 10 additions & 4 deletions cmd/nebel/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func runAddFile(args []string) error {
if len(args) != 1 {
return fmt.Errorf("usage: nebel add file <glob>")
}
glob := args[0]
glob := normalizePath(args[0])

root, configPath, cfg, err := openConfig()
if err != nil {
Expand Down Expand Up @@ -103,15 +103,15 @@ func runAddField(args []string) error {
if len(args) == 0 {
return fmt.Errorf("usage: nebel add field <file> [path...]")
}
target, paths := args[0], args[1:]
target, paths := normalizePath(args[0]), args[1:]

root, configPath, cfg, err := openConfig()
if err != nil {
return err
}

// The rule pattern is stored as given, but the document is read from
// the working tree, so the argument has to name a real file.
// The document is read from the working tree, so the argument has to
// name a real file.
source, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(target)))
if err != nil {
return fmt.Errorf("reading %s: %w", target, err)
Expand Down Expand Up @@ -154,6 +154,12 @@ func runAddField(args []string) error {
return nil
}

// normalizePath gives a CLI path or glob the repo-relative, slash-separated
// form git reports, so it matches what MatchRule later compares it against.
func normalizePath(path string) string {
return filepath.ToSlash(filepath.Clean(path))
}

// existingFields returns the paths already configured for pattern.
func existingFields(cfg *config.Config, pattern string) map[string]bool {
fields := map[string]bool{}
Expand Down
56 changes: 56 additions & 0 deletions cmd/nebel/addfield_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,62 @@ func TestAddFieldRejectsMissingPath(t *testing.T) {
}
}

// issue #8: a leading "./" on the path must be normalized away before it
// reaches .gitattributes and the config, or the pattern silently fails to
// match the file git reports without it.
func TestAddFieldNormalizesRelativePath(t *testing.T) {
pathEnv := pathEnvWithBin(t)
repo := setupValueRepo(t, pathEnv)

runIn(t, repo, pathEnv, "nebel", "add", "field", "./config/staging.yaml", "database.password")

attrs, err := os.ReadFile(filepath.Join(repo, gitattributesName))
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(attrs), "./config/staging.yaml") {
t.Errorf("%s still has an un-normalized ./ prefix:\n%s", gitattributesName, attrs)
}
if !strings.Contains(string(attrs), "config/staging.yaml filter=nebel") {
t.Errorf("%s is missing the normalized pattern:\n%s", gitattributesName, attrs)
}

cfg, err := os.ReadFile(filepath.Join(repo, ".nebel.yaml"))
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(cfg), "./config/staging.yaml") {
t.Errorf(".nebel.yaml still has an un-normalized ./ prefix:\n%s", cfg)
}

runIn(t, repo, pathEnv, "git", "add", ".")
runIn(t, repo, pathEnv, "git", "commit", "-q", "-m", "encrypt fields")
stored := runIn(t, repo, pathEnv, "git", "show", "HEAD:config/staging.yaml")
if strings.Contains(stored, "s3cr3t") {
t.Errorf("path given with a ./ prefix was not actually encrypted:\n%s", stored)
}
}

// issue #8, mode: file variant: the same normalization applies to
// `add file`'s glob argument.
func TestAddFileNormalizesRelativePath(t *testing.T) {
pathEnv := pathEnvWithBin(t)
repo := setupValueRepo(t, pathEnv)

runIn(t, repo, pathEnv, "nebel", "add", "file", "./config/*.yaml")

attrs, err := os.ReadFile(filepath.Join(repo, gitattributesName))
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(attrs), "./config/*.yaml") {
t.Errorf("%s still has an un-normalized ./ prefix:\n%s", gitattributesName, attrs)
}
if !strings.Contains(string(attrs), "config/*.yaml filter=nebel") {
t.Errorf("%s is missing the normalized pattern:\n%s", gitattributesName, attrs)
}
}

// The two subcommands write different rules for the same path, so mixing
// them is refused rather than silently reinterpreting one as the other.
func TestAddFileAndFieldConflict(t *testing.T) {
Expand Down
Loading