Configuration System#
Overview#
The opnDossier Configuration System is a Viper-based layered configuration management system that provides flexible, hierarchical configuration through multiple sources. The system implements a strict four-level precedence model where command-line flags take highest priority, followed by environment variables, configuration files, and finally built-in defaults. This architecture enables developers to maintain persistent settings in configuration files while allowing easy overrides through environment variables for deployment scenarios and temporary adjustments via CLI flags.
Built on the spf13/viper configuration library and integrated with the Cobra CLI framework, the system provides comprehensive validation, structured error reporting, and specialized configuration management commands. The configuration layer is responsible for configuration file parsing (YAML), environment variable processing with the OPNDOSSIER_ prefix, command-line flag integration, and configuration precedence management.
The system supports both flat and nested configuration structures for backward compatibility, includes format aliases for common output types, enforces mutually exclusive flag constraints, and provides dedicated commands (config init, config show, config validate) for configuration file management and validation.
Architecture and Design#
Core Technology Stack#
The configuration system is built on two primary frameworks:
- Viper - Handles layered configuration sources including files, environment variables, and flags, with automatic environment variable processing and configuration precedence management
- Cobra - Provides command-line argument parsing, flag definitions, and command hierarchy management
Configuration Precedence Model#
The system implements a strict four-level precedence hierarchy:
| Priority | Source | Description | Override Behavior |
|---|---|---|---|
| 1 (Highest) | CLI Flags | Direct command-line arguments | Overrides all other sources |
| 2 | Environment Variables | Variables with OPNDOSSIER_ prefix | Overrides config file and defaults |
| 3 | Configuration File | YAML file at ~/.opnDossier.yaml | Overrides defaults only |
| 4 (Lowest) | Built-in Defaults | Hardcoded sensible defaults | Used when no other source provides value |
Design Principles#
The configuration system follows standard precedence ordering used by most CLI tools, where environment variables can override configuration file settings for deployment flexibility and security. This design enables:
- Development flexibility - Use configuration files for persistent settings
- Deployment adaptability - Override settings via environment variables in containerized environments
- Debugging convenience - Temporarily override any setting with CLI flags
- Security - Keep sensitive values in environment variables rather than files
Configuration Sources#
Configuration Files#
The default configuration file location is ~/.opnDossier.yaml. You can specify a custom location using the --config flag.
Basic Configuration Example:
# ~/.opnDossier.yaml
# Output settings
verbose: false
quiet: false
format: markdown
wrap: 120
# Display settings
display:
width: 120
pager: false
syntax_highlighting: true
# Logging settings
logging:
level: info
format: text
# Export settings
export:
format: markdown
directory: ""
backup: false
# Validation settings
validation:
strict: false
schema_validation: false
Using Custom Configuration File:
opndossier --config /path/to/custom/config.yaml convert config.xml
Environment Variables#
All configuration options can be set using environment variables with the OPNDOSSIER_ prefix.
Naming Convention#
Environment variables follow this pattern:
- Add
OPNDOSSIER_prefix - Convert key to uppercase
- Replace hyphens (
-) and dots (.) with underscores (_)
Transformation Examples:
| Configuration Key | Environment Variable |
|---|---|
verbose | OPNDOSSIER_VERBOSE |
input_file | OPNDOSSIER_INPUT_FILE |
no_progress | OPNDOSSIER_NO_PROGRESS |
display.width | OPNDOSSIER_DISPLAY_WIDTH |
logging.level | OPNDOSSIER_LOGGING_LEVEL |
export.format | OPNDOSSIER_EXPORT_FORMAT |
Value Formats#
Boolean Values:
Boolean environment variables accept various formats:
- True values:
true,TRUE,True,1 - False values:
false,FALSE,False,0
export OPNDOSSIER_VERBOSE=true
export OPNDOSSIER_QUIET=false
List Values:
List values use comma-separated strings:
export OPNDOSSIER_SECTIONS="system,network,firewall,dhcp"
String and Numeric Values:
export OPNDOSSIER_FORMAT=json
export OPNDOSSIER_WRAP=120
export OPNDOSSIER_LOGGING_LEVEL=debug
Comprehensive Environment Variable Reference#
The system supports environment variables for all flat and nested configuration options:
Basic Settings:
OPNDOSSIER_VERBOSEOPNDOSSIER_QUIETOPNDOSSIER_FORMATOPNDOSSIER_WRAPOPNDOSSIER_MINIMALOPNDOSSIER_NO_PROGRESS
Display Settings:
OPNDOSSIER_DISPLAY_WIDTHOPNDOSSIER_DISPLAY_PAGEROPNDOSSIER_DISPLAY_SYNTAX_HIGHLIGHTING
Export Settings:
OPNDOSSIER_EXPORT_FORMATOPNDOSSIER_EXPORT_DIRECTORYOPNDOSSIER_EXPORT_BACKUP
Logging Settings:
OPNDOSSIER_LOGGING_LEVELOPNDOSSIER_LOGGING_FORMAT
Validation Settings:
OPNDOSSIER_VALIDATION_STRICTOPNDOSSIER_VALIDATION_SCHEMA_VALIDATION
Validate Command Settings:
OPNDOSSIER_JSON_OUTPUT- Output validation errors in JSON format (validate command only)
Command-Line Flags#
Global Flags#
Global flags are available on all commands:
| Flag | Short | Default | Description |
|---|---|---|---|
--config | "" | Custom config file path | |
--verbose | -v | false | Enable debug-level logging |
--quiet | -q | false | Suppress all output except errors |
--color | "auto" | Color output mode (auto, always, never) | |
--no-progress | false | Disable progress indicators | |
--timestamps | false | Include timestamps in log output | |
--minimal | false | Minimal output mode |
Example Usage:
# Enable verbose logging
opndossier --verbose convert config.xml
# Use custom config file
opndossier --config ~/configs/production.yaml convert config.xml
# Suppress progress bars
opndossier --no-progress convert config.xml
Command-Specific Flags#
Convert Command:
Command-specific flags for convert:
| Flag | Short | Default | Description |
|---|---|---|---|
--output | -o | "" | Output file path (default: stdout) |
--format | -f | "markdown" | Output format |
--force | false | Force overwrite without prompt | |
--section | [] | Sections to include | |
--wrap | -1 | Text wrap width (-1=auto, 0=off) | |
--no-wrap | false | Disable text wrapping | |
--comprehensive | false | Generate comprehensive reports | |
--include-tunables | false | Include all system tunables in report output (markdown, text, HTML only; JSON/YAML always include all tunables) |
Display Command:
Command-specific flags for display:
| Flag | Short | Default | Description |
|---|---|---|---|
--theme | "" | Rendering theme (auto, dark, light, none) | |
--section | [] | Sections to include | |
--wrap | -1 | Text wrap width | |
--no-wrap | false | Disable text wrapping |
Validate Command:
Command-specific flags for validate:
| Flag | Short | Default | Description |
|---|---|---|---|
--json-output | false | Output validation errors in JSON format |
Validation and Constraints#
Mutually Exclusive Flags#
The system enforces mutually exclusive constraints on certain flag combinations:
Verbose and Quiet:
--verbose and --quiet are mutually exclusive. This constraint is enforced using Cobra's native MarkFlagsMutuallyExclusive() feature:
# Error: these flags cannot be used together
opndossier --verbose --quiet convert config.xml
# Error: if any flags in the group [verbose quiet] are set none of the others can be
Wrap and No-Wrap:
--wrap and --no-wrap are mutually exclusive. This is validated using manual flag checking:
# Error: these flags cannot be used together
opndossier convert config.xml --wrap 120 --no-wrap
# Error: --no-wrap and --wrap flags are mutually exclusive
Global Flag Constraints#
Various constraints are applied to global flag values:
Color Mode:
--color must be one of: auto, always, never
# Valid
opndossier --color always convert config.xml
# Invalid
opndossier --color invalid convert config.xml
# Error: invalid color "invalid", must be one of: auto, always, never
Theme:
--theme must be one of: auto, dark, light, none
Note: The theme flag is command-specific to the display command, not a global flag.
Format:
--format must be one of: markdown, md, json, yaml, yml, text, txt, html, htm
Format Aliases#
The system supports format aliases for common formats. Format aliases are defined as constants and normalized via the normalizeFormat() function:
| Alias | Canonical Format |
|---|---|
md | markdown |
yml | yaml |
txt | text |
htm | html |
Example Usage:
# These are equivalent
opndossier convert config.xml --format markdown
opndossier convert config.xml --format md
# These are equivalent
opndossier convert config.xml --format yaml
opndossier convert config.xml --format yml
Wrap Width Constraints#
The --wrap flag has specific constraints. Constants define the recommended range:
- Recommended range: 40-200 columns (ideal: 80-120)
- Special values:
-1- Auto-detect terminal width0- Disable wrapping- Positive integers - Specific column width
Values outside 40-200 trigger warnings but are still accepted. Values less than -1 trigger errors.
# Recommended range (no warning)
opndossier convert config.xml --wrap 120
# Outside recommended range (warning issued)
opndossier convert config.xml --wrap 250
# Warning: wrap width 250 is outside recommended range [40, 200]
# Invalid value (error)
opndossier convert config.xml --wrap -5
# Error: invalid wrap width -5: must be -1 (auto-detect), 0 (no wrapping), or positive
Validation Error Messages#
The system provides clear validation error messages when constraints are violated:
| Validation Error | Solution |
|---|---|
| "invalid format" | Use: markdown, md, json, yaml, yml, text, txt, html, or htm |
| "invalid theme value" | Use: light, dark, auto, none, or custom |
| "invalid log level" | Use: debug, info, warn, or error |
| "wrap width must be >= -1" | Use -1 (auto), 0 (no wrap), or positive number |
| "mutually exclusive flags" | Remove one of the conflicting flags |
Configuration Management Commands#
config init#
The config init command generates a template configuration file.
Basic Usage:
# Create default config at ~/.opnDossier.yaml
opndossier config init
Advanced Options:
# Create config at specific path
opndossier config init --output /path/to/config.yaml
# Overwrite existing config
opndossier config init --force
config show#
The config show command displays the current effective configuration, showing the merged result of all configuration sources according to precedence rules.
Basic Usage:
# Show all configuration values (styled terminal output)
opndossier config show
JSON Output for Scripting:
# Show config in JSON format
opndossier config show --json
This command is useful for:
- Debugging configuration issues
- Verifying which settings are active
- Understanding precedence behavior
- Generating configuration documentation
config validate#
The config validate command validates configuration files, checking for syntax errors, invalid values, and constraint violations.
Basic Usage:
# Validate default config file
opndossier config validate
# Validate specific config file
opndossier config validate --config /path/to/config.yaml
Common Validation Errors:
The system provides detailed validation error information:
| Error | Cause | Solution |
|---|---|---|
| "invalid theme value" | Theme not in allowed list | Use: light, dark, auto, none, or custom |
| "invalid format" | Format not recognized | Use: markdown, md, json, yaml, or yml |
| "invalid log level" | Log level not valid | Use: debug, info, warn, or error |
| "wrap width must be >= -1" | Wrap value out of bounds | Use -1 (auto), 0 (no wrap), or positive |
Implementation Architecture#
Configuration Struct Design#
The configuration uses a hybrid design with both flat fields for backward compatibility and nested structs with mapstructure tags for Viper integration:
type Config struct {
// Flat fields (backward compatible)
InputFile string `mapstructure:"input_file"`
OutputFile string `mapstructure:"output_file"`
Verbose bool `mapstructure:"verbose"`
Quiet bool `mapstructure:"quiet"`
Format string `mapstructure:"format"`
Theme string `mapstructure:"theme"`
WrapWidth int `mapstructure:"wrap"`
Sections []string `mapstructure:"sections"`
JSONOutput bool `mapstructure:"json_output"`
Minimal bool `mapstructure:"minimal"`
NoProgress bool `mapstructure:"no_progress"`
// Nested configuration sections
Display DisplayConfig `mapstructure:"display"`
Export ExportConfig `mapstructure:"export"`
Logging LoggingConfig `mapstructure:"logging"`
Validation ValidationConfig `mapstructure:"validation"`
}
type DisplayConfig struct {
Width int `mapstructure:"width"`
Pager bool `mapstructure:"pager"`
SyntaxHighlighting bool `mapstructure:"syntax_highlighting"`
}
type LoggingConfig struct {
Level string `mapstructure:"level"`
Format string `mapstructure:"format"`
}
Configuration Loading Process#
Three loading functions provide increasing flexibility:
LoadConfig(cfgFile string)- Default loading with ViperLoadConfigWithFlags(cfgFile string, flags *pflag.FlagSet)- Binds CLI flags for proper precedenceLoadConfigWithViper(cfgFile string, v *viper.Viper)- Core implementation with custom Viper instance
Viper Initialization:
The LoadConfigWithViper function sets up the configuration system:
func LoadConfigWithViper(cfgFile string, v *viper.Viper) (*Config, error) {
// Set up environment variable handling
v.SetEnvPrefix("OPNDOSSIER")
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
v.AutomaticEnv()
// Configure config file settings
if cfgFile != "" {
v.SetConfigFile(cfgFile)
} else {
home, _ := os.UserHomeDir()
v.AddConfigPath(home)
v.SetConfigType("yaml")
v.SetConfigName(".opnDossier")
}
// Read config file if it exists
v.ReadInConfig()
// Set default values
v.SetDefault("format", "markdown")
v.SetDefault("wrap", -1)
v.SetDefault("logging.level", "info")
// ... more defaults
// Unmarshal into Config struct
cfg := &Config{}
v.Unmarshal(cfg)
return cfg, nil
}
Nested Environment Variable Bindings:
Explicit nested env bindings are required because Viper's AutomaticEnv() doesn't automatically resolve nested keys:
// Bind environment variables for nested configuration keys
nestedEnvBindings := map[string]string{
"display.width": "DISPLAY_WIDTH",
"display.pager": "DISPLAY_PAGER",
"display.syntax_highlighting": "DISPLAY_SYNTAX_HIGHLIGHTING",
"export.format": "EXPORT_FORMAT",
"logging.level": "LOGGING_LEVEL",
"logging.format": "LOGGING_FORMAT",
"validation.strict": "VALIDATION_STRICT",
}
for key, envSuffix := range nestedEnvBindings {
v.BindEnv(key, "OPNDOSSIER_"+envSuffix)
}
Validation System#
The Validator struct performs comprehensive validation:
type Validator struct {
errors *MultiValidationError
config *Config
}
func (v *Validator) Validate() *MultiValidationError {
v.validateFlags()
v.validateInputFile()
v.validateOutputFile()
v.validateTheme()
v.validateFormat()
v.validateWrapWidth()
v.validateDisplayConfig()
v.validateExportConfig()
v.validateLoggingConfig()
v.validateValidationConfig()
if v.errors.HasErrors() {
return v.errors
}
return nil
}
Validation Types:
- Enum validation - Checks against valid value lists
- Range validation - Ensures numeric bounds
- File path validation - Verifies file existence
- Directory validation - Checks directory accessibility
Valid values are defined as package constants:
var ValidLogLevels = []string{"debug", "info", "warn", "error"}
var ValidThemes = []string{"light", "dark", "auto", "none", "custom", ""}
var ValidFormats = []string{"markdown", "md", "json", "yaml", "yml", "text", "txt", "html", "htm", ""}
CLI Integration with Cobra#
The root command uses a two-path initialization strategy in PersistentPreRunE to optimize startup time:
Lightweight Path (for version, help, completion):
Creates minimal context without config loading
Full Path (for normal commands):
Performs complete initialization:
- Load config with flag binding:
config.LoadConfigWithFlags() - Determine log level from verbose/quiet flags
- Initialize logger with proper configuration
- Validate global flags
- Create CommandContext with Config and Logger
- Set context on command for subcommand access
CommandContext Pattern#
The CommandContext provides explicit dependency injection, eliminating global state:
type CommandContext struct {
Config *config.Config
Logger *logging.Logger
}
// Usage in commands
func runCommand(cmd *cobra.Command, args []string) error {
cmdCtx := GetCommandContext(cmd)
if cmdCtx == nil {
return errors.New("command context not initialized")
}
// Access configuration
format := cmdCtx.Config.Format
// Use logger
cmdCtx.Logger.Info("running command", "format", format)
return nil
}
This pattern provides:
- Explicit dependencies (no hidden global state)
- Easier testing through context injection
- Clear data flow traceability
- Better support for concurrent command execution
Best Practices#
Use Configuration Files for Persistent Settings#
Store frequently used settings in ~/.opnDossier.yaml for consistent behavior across sessions:
# Development configuration
verbose: true
format: markdown
wrap: 120
logging:
level: debug
format: text
display:
syntax_highlighting: true
width: 120
validation:
strict: true
Use Environment Variables for Deployment#
For automated scripts and CI/CD pipelines, use environment variables to adapt configuration without modifying files:
#!/bin/bash
# CI/CD pipeline configuration
export OPNDOSSIER_QUIET=true
export OPNDOSSIER_VALIDATION_STRICT=true
export OPNDOSSIER_NO_PROGRESS=true
export OPNDOSSIER_FORMAT=json
opndossier convert config.xml -o report.json
Production Configuration Example:
Example production configuration:
verbose: false
quiet: false
minimal: true
no_progress: true
format: markdown
export:
format: markdown
backup: true
directory: /var/reports/opnsense
logging:
level: warn
format: json
validation:
strict: true
Use CLI Flags for One-off Overrides#
For temporary debugging or testing, use CLI flags:
# Debug a specific run
opndossier --verbose convert problematic-config.xml
# Test different output formats
opndossier convert config.xml --format json -o test.json
# Override configured wrap width
opndossier convert config.xml --wrap 80
Development vs. Production Configurations#
Development Configuration:
Example development configuration:
verbose: true
logging:
level: debug
format: text
validation:
strict: true
display:
syntax_highlighting: true
width: 120
CI/CD Pipeline Configuration:
export OPNDOSSIER_QUIET=true
export OPNDOSSIER_VALIDATION_STRICT=true
export OPNDOSSIER_NO_PROGRESS=true
opndossier validate config.xml
opndossier convert config.xml -o report.md
Troubleshooting#
Configuration File Not Found#
Symptoms:
Configuration file is not being loaded or settings are not applied.
Solutions:
# Verify config file exists
ls -la ~/.opnDossier.yaml
# Check file permissions (should be readable)
chmod 644 ~/.opnDossier.yaml
# Explicitly specify config path
opndossier --config ~/.opnDossier.yaml convert config.xml
# Use verbose mode to see config file path
opndossier --verbose convert config.xml
Environment Variables Not Working#
Symptoms:
Environment variables are set but not being applied.
Solutions:
- Check prefix is correct:
# List all OPNDOSSIER environment variables
env | grep OPNDOSSIER
- Verify variable names match exactly:
# Correct
export OPNDOSSIER_VERBOSE=true
# Wrong (missing underscore)
export OPNDOSSIERVERBOSE=true
- For nested config, use underscore separator:
# Correct
export OPNDOSSIER_DISPLAY_WIDTH=120
# Wrong (using dot)
export OPNDOSSIER_DISPLAY.WIDTH=120
CLI Flags Not Overriding Config#
Symptoms:
CLI flags appear to be ignored, with config file values taking precedence.
Solutions:
- Verify flag syntax is correct:
# Check for typos
opndossier convert config.xml --format json # Correct
opndossier convert config.xml --fromat json # Wrong
- Global flags must come before the command:
# Correct
opndossier --config custom.yaml --verbose convert config.xml
# Wrong (global flags after command)
opndossier convert config.xml --config custom.yaml --verbose
- Check for flag validation errors:
# Invalid values will be rejected
opndossier convert config.xml --format invalid
# Error: invalid format "invalid", must be one of: markdown, md, json, yaml, yml, text, txt, html, htm
Debug Configuration Loading#
Use verbose mode to see configuration details:
opndossier --verbose --config /path/to/config.yaml convert config.xml
This displays:
- Configuration file path being used
- Detected environment variables
- Final merged configuration values
- Validation results
Use config show to see effective configuration:
opndossier config show
opndossier config show --json # For programmatic inspection
Complete Configuration Reference#
Configuration Options#
The system supports the following configuration options:
| Option | Type | Default | Description |
|---|---|---|---|
input_file | string | "" | Default input file path |
output_file | string | "" | Default output file path |
verbose | boolean | false | Enable verbose/debug logging |
quiet | boolean | false | Suppress all output except errors |
format | string | "markdown" | Output format (markdown, json, yaml, text, html) |
theme | string | "" | Display theme (auto, dark, light, none) |
wrap | int | -1 | Text wrap width (-1=auto, 0=off, >0=columns) |
sections | string[] | [] | Sections to include in output |
no_progress | boolean | false | Disable progress indicators |
minimal | boolean | false | Minimal output mode |
Validate Command Options:
| Option | Type | Default | Description |
|---|---|---|---|
json_output | boolean | false | Output validation errors in JSON format (validate command only) |
Nested Configuration#
Display Settings:
| Option | Type | Default | Description |
|---|---|---|---|
display.width | int | -1 | Display width (-1=auto-detect) |
display.pager | boolean | false | Enable pager for output |
display.syntax_highlighting | boolean | true | Enable syntax highlighting |
Export Settings:
| Option | Type | Default | Description |
|---|---|---|---|
export.format | string | "markdown" | Export format |
export.directory | string | "" | Export directory path |
export.backup | boolean | false | Create backup before export |
Logging Settings:
| Option | Type | Default | Description |
|---|---|---|---|
logging.level | string | "info" | Log level (debug, info, warn, error) |
logging.format | string | "text" | Log format (text, json) |
Validation Settings:
| Option | Type | Default | Description |
|---|---|---|---|
validation.strict | boolean | false | Enable strict validation mode |
validation.schema_validation | boolean | false | Enable schema validation |
Adding New Configuration Options#
Based on CONTRIBUTING.md guidance, follow these steps:
- Add to Config struct - Define field with
mapstructuretag in internal/config/config.go - Set default value - Use
v.SetDefault()in LoadConfigWithViper - Add CLI flag - Register flag in command's flag definitions
- Add validation - Implement validation in Validator methods
- Update documentation - Update README, configuration guides, and help text
Implementation File Reference#
| File Path | Description | Key Components |
|---|---|---|
| internal/config/config.go | Core configuration management | Config struct, LoadConfig functions, Viper setup, default values |
| internal/config/validation.go | Configuration validation | Validator struct, enum/range/path validation, valid value constants |
| internal/config/errors.go | Validation error types | FieldValidationError, MultiValidationError with structured messages |
| cmd/root.go | Root command and global flags | PersistentFlags, two-path initialization, global flag definitions |
| cmd/context.go | Dependency injection pattern | CommandContext, Get/SetCommandContext helpers |
| cmd/shared_flags.go | Shared flag definitions | MinWrapWidth, MaxWrapWidth constants, flag completion |
| cmd/convert.go | Convert command implementation | Format aliases constants, normalizeFormat(), flag validation |
~/.opnDossier.yaml | Default user configuration file | YAML configuration file location |
Related Topics#
Viper Configuration Library#
The Viper library by spf13 provides the foundation for opnDossier's configuration management. Viper supports:
- Multiple configuration formats (JSON, TOML, YAML, HCL, etc.)
- Reading from environment variables
- Reading from remote config systems (etcd, Consul)
- Live watching and re-reading of config files
- Setting explicit values
Cobra CLI Framework#
The Cobra library by spf13 provides the CLI command structure. Cobra features:
- POSIX-compliant flags (including short & long versions)
- Nested subcommands
- Global, local and cascading flags
- Intelligent suggestions (did you mean?)
- Automatic help generation
- Shell completion (bash, zsh, fish, powershell)
CLI Architecture Patterns#
The configuration system demonstrates several important CLI architecture patterns:
- Layered configuration with clear precedence - Standard pattern for CLI tools
- Environment variable conventions - Uppercase with prefix and underscores
- CommandContext dependency injection - Explicit dependencies over global state
- Two-path initialization - Performance optimization for lightweight commands
- Structured validation with helpful error messages - User-friendly error reporting