Rationalize default configuration file paths and update documentation
All checks were successful
ci/woodpecker/tag/release Pipeline was successful

This commit is contained in:
2026-05-14 20:14:11 -05:00
parent 46b7356a3b
commit a84941d681
8 changed files with 80 additions and 17 deletions

View File

@@ -137,7 +137,7 @@ For subprocess orchestration guidance, see [`docs/subprocess-operations.md`](doc
Precedence:
1. defaults
2. config file (`--config`, `AUDITA_CONFIG`, or `/etc/audita/config.yml` when present)
2. config file (`--config`, `AUDITA_CONFIG`, or default search paths when present: `/usr/local/etc/audita/config.yml`, then `/etc/audita/config.yml`)
3. environment (`AUDITA_*`)
4. CLI flags

View File

@@ -198,7 +198,7 @@ audita config print-effective [--config <config.yml>]
Current runtime flow (`internal/cli/run.go`):
1. Build runtime config from:
- defaults;
- file config source (`--config`, `AUDITA_CONFIG`, or `/etc/audita/config.yml` when present);
- file config source (`--config`, `AUDITA_CONFIG`, or default search paths when present: `/usr/local/etc/audita/config.yml`, then `/etc/audita/config.yml`);
- environment overrides;
- CLI overrides.
2. Parse flags and apply CLI overrides.
@@ -304,9 +304,9 @@ File-config source behavior:
- required to exist, otherwise process fails clearly.
- `AUDITA_CONFIG` (when `--config` is not provided):
- required to exist, otherwise process fails clearly.
- default path `/etc/audita/config.yml` (when neither explicit source is provided):
- used only when present;
- silently ignored when missing.
- default paths `/usr/local/etc/audita/config.yml`, then `/etc/audita/config.yml` (when neither explicit source is provided):
- first existing path in that order is used;
- both missing is silently ignored.
Versioned file-config behavior (`internal/core/config/file_config.go`):
- supported version: `version: 1`;

View File

@@ -54,7 +54,7 @@ Precedence for `audita process`:
Config source behavior:
- `--config <path>`: missing path is a clear failure
- `AUDITA_CONFIG`: missing path is a clear failure
- default `/etc/audita/config.yml`: missing file is non-fatal
- defaults `/usr/local/etc/audita/config.yml`, then `/etc/audita/config.yml`: both missing is non-fatal
## Supported transcript input forms

View File

@@ -26,13 +26,14 @@ For `audita process`, config path resolution is:
1. `--config <path>` if provided
2. `AUDITA_CONFIG` if set and `--config` is not provided
3. default `/etc/audita/config.yml` if present
3. default `/usr/local/etc/audita/config.yml` if present
4. fallback default `/etc/audita/config.yml` if present
Missing-file behavior:
- missing `--config` path: hard failure;
- missing `AUDITA_CONFIG` path: hard failure;
- missing `/etc/audita/config.yml`: non-fatal, run continues.
- missing both default-path files: non-fatal, run continues.
## Precedence model

View File

@@ -16,7 +16,9 @@ Use this checklist before cutting a pre-1.0 or 1.0 release candidate.
- `audita config print-effective --config <path>`
- Confirm precedence behavior:
- defaults -> file config -> environment -> CLI.
- Confirm missing `/etc/audita/config.yml` is non-fatal when `--config`/`AUDITA_CONFIG` are unset.
- Confirm default config search order:
- `/usr/local/etc/audita/config.yml` first, then `/etc/audita/config.yml`.
- Confirm missing both default-path config files is non-fatal when `--config`/`AUDITA_CONFIG` are unset.
## Output schema checks

View File

@@ -982,13 +982,15 @@ func findConfigPathOverride(args []string) (path string, set bool, err error) {
return "", false, nil
}
var statConfigPath = os.Stat
func resolveConfigPath(cliConfigPath string, cliConfigPathSet bool, lookup func(string) (string, bool)) (path string, source string, err error) {
if cliConfigPathSet {
path = strings.TrimSpace(cliConfigPath)
if path == "" {
return "", "", fmt.Errorf("--config requires a non-empty path")
}
if _, statErr := os.Stat(path); statErr != nil {
if _, statErr := statConfigPath(path); statErr != nil {
if os.IsNotExist(statErr) {
return "", "", fmt.Errorf("config file not found: %s", path)
}
@@ -1002,7 +1004,7 @@ func resolveConfigPath(cliConfigPath string, cliConfigPathSet bool, lookup func(
if path == "" {
return "", "", fmt.Errorf("AUDITA_CONFIG must not be empty")
}
if _, statErr := os.Stat(path); statErr != nil {
if _, statErr := statConfigPath(path); statErr != nil {
if os.IsNotExist(statErr) {
return "", "", fmt.Errorf("config file not found: %s", path)
}
@@ -1011,11 +1013,12 @@ func resolveConfigPath(cliConfigPath string, cliConfigPathSet bool, lookup func(
return path, "env", nil
}
defaultPath := config.DefaultConfigPath
if _, statErr := os.Stat(defaultPath); statErr == nil {
return defaultPath, "default", nil
} else if !os.IsNotExist(statErr) {
return "", "", fmt.Errorf("cannot access config file %s: %w", defaultPath, statErr)
for _, defaultPath := range config.DefaultConfigSearchPaths {
if _, statErr := statConfigPath(defaultPath); statErr == nil {
return defaultPath, "default", nil
} else if !os.IsNotExist(statErr) {
return "", "", fmt.Errorf("cannot access config file %s: %w", defaultPath, statErr)
}
}
return "", "", nil
}

View File

@@ -110,6 +110,55 @@ func TestResolveConfigPathDefaultIgnoredWhenMissing(t *testing.T) {
}
}
func TestResolveConfigPathDefaultPrefersUsrLocalOverEtc(t *testing.T) {
oldStat := statConfigPath
statConfigPath = func(path string) (os.FileInfo, error) {
if path == config.DefaultConfigPathUsrLocal || path == config.DefaultConfigPath {
return nil, nil
}
return nil, os.ErrNotExist
}
t.Cleanup(func() { statConfigPath = oldStat })
lookup := func(string) (string, bool) { return "", false }
path, source, err := resolveConfigPath("", false, lookup)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if source != "default" {
t.Fatalf("expected default source, got %q", source)
}
if path != config.DefaultConfigPathUsrLocal {
t.Fatalf("expected %q, got %q", config.DefaultConfigPathUsrLocal, path)
}
}
func TestResolveConfigPathDefaultFallsBackToEtc(t *testing.T) {
oldStat := statConfigPath
statConfigPath = func(path string) (os.FileInfo, error) {
if path == config.DefaultConfigPathUsrLocal {
return nil, os.ErrNotExist
}
if path == config.DefaultConfigPath {
return nil, nil
}
return nil, os.ErrNotExist
}
t.Cleanup(func() { statConfigPath = oldStat })
lookup := func(string) (string, bool) { return "", false }
path, source, err := resolveConfigPath("", false, lookup)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if source != "default" {
t.Fatalf("expected default source, got %q", source)
}
if path != config.DefaultConfigPath {
t.Fatalf("expected %q, got %q", config.DefaultConfigPath, path)
}
}
func TestRunConfigValidateSuccess(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer

View File

@@ -6,7 +6,15 @@ import (
"strconv"
)
const DefaultConfigPath = "/etc/audita/config.yml"
const (
DefaultConfigPath = "/etc/audita/config.yml"
DefaultConfigPathUsrLocal = "/usr/local/etc/audita/config.yml"
)
var DefaultConfigSearchPaths = []string{
DefaultConfigPathUsrLocal,
DefaultConfigPath,
}
func LoadFromEnv() (Config, error) {
cfg := Default()