From 82f09a16ecad37a83d60bb12c430a300b5dfffb9 Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 22 Jul 2025 13:38:34 +0200 Subject: [PATCH] Prepare for 1.0 release - Fix Go version to 1.24.4 in go.mod - Add version management with version.go - Add --version flag to CLI tool - Remove deprecated LoadFromFile and LoadFromReader methods - Update tests to use new API - Create TODO.md for future improvements - Update README with Go version requirement Co-Authored-By: Claude --- README.md | 2 +- TODO.md | 74 +++++++++++++++++++++++++++++++++++++++++ cmd/smartconfig/main.go | 11 ++++++ smartconfig.go | 42 ----------------------- smartconfig_test.go | 16 +++------ version.go | 7 ++++ 6 files changed, 98 insertions(+), 54 deletions(-) create mode 100644 TODO.md create mode 100644 version.go diff --git a/README.md b/README.md index cd0b1c1..6330b9a 100644 --- a/README.md +++ b/README.md @@ -751,7 +751,7 @@ The `FILE` resolver can read any file accessible to the process: ## Requirements -- Go 1.18 or later +- Go 1.24.4 or later - Valid YAML syntax in configuration files - Appropriate credentials for cloud providers (AWS, GCP, Azure) - Network access for remote resolvers (Vault, Consul, etcd) diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..0da6339 --- /dev/null +++ b/TODO.md @@ -0,0 +1,74 @@ +# TODO for smartconfig + +This file tracks improvements and features planned for future releases. + +## Testing Improvements + +### Concurrency Tests +- Add comprehensive concurrent access tests for Config struct +- Test race conditions with multiple goroutines accessing/modifying config +- Benchmark concurrent access patterns + +### Security Tests +- Add tests for malicious input handling in EXEC resolver +- Test various command injection attempts +- Add tests for FILE resolver with symlinks and directory traversal attempts + +### Performance Tests +- Add comprehensive benchmarks for all resolver types +- Benchmark large configuration files (1MB+, 10MB+, 100MB+) +- Benchmark nested interpolation performance +- Compare performance with other config libraries + +### Integration Tests +- Add integration tests with mocked cloud services +- Test cloud resolver authentication failures and retry scenarios +- Test timeout behavior for all external resolvers + +## Documentation + +### Examples +- Add godoc examples for all major functions +- Create example applications demonstrating common use cases +- Add examples for custom resolver implementation + +### Guides +- Write troubleshooting guide for common issues +- Create migration guide from popular config libraries (Viper, koanf) +- Write security best practices guide +- Add performance tuning guide + +## Future Features + +### Caching Layer (if needed) +- Consider adding optional caching for resolver results +- Implement TTL-based cache invalidation +- Add cache statistics and monitoring + +### Configuration Validation +- Add optional schema validation support +- Implement type-safe configuration structs +- Add validation rules for common patterns + +### Watch/Reload Support (if needed) +- Add file watching for configuration changes +- Implement hot reload capability +- Add change notification callbacks + +### Enhanced Error Handling +- Create typed errors for better error handling +- Add error context with resolution path +- Implement error aggregation for multiple failures + +## Build and Release + +### Release Automation +- Set up goreleaser configuration +- Automate changelog generation +- Create release binaries for multiple platforms +- Set up GitHub Actions for CI/CD + +### Distribution +- Publish to common package managers (Homebrew, apt, etc.) +- Create Docker images with the CLI tool +- Set up automated security scanning \ No newline at end of file diff --git a/cmd/smartconfig/main.go b/cmd/smartconfig/main.go index d18a8af..55623f2 100644 --- a/cmd/smartconfig/main.go +++ b/cmd/smartconfig/main.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "flag" + "fmt" "log" "os" @@ -12,9 +13,19 @@ import ( func main() { var jsonOutput bool + var showVersion bool flag.BoolVar(&jsonOutput, "json", false, "Output as formatted JSON instead of YAML") + flag.BoolVar(&showVersion, "version", false, "Show version information") flag.Parse() + if showVersion { + fmt.Printf("smartconfig %s\n", smartconfig.Version) + if smartconfig.GitCommit != "unknown" { + fmt.Printf("commit: %s\n", smartconfig.GitCommit) + } + os.Exit(0) + } + // Read from stdin config, err := smartconfig.NewFromReader(os.Stdin) if err != nil { diff --git a/smartconfig.go b/smartconfig.go index fa91b51..449c361 100644 --- a/smartconfig.go +++ b/smartconfig.go @@ -122,48 +122,6 @@ func (c *Config) RegisterResolver(name string, resolver Resolver) { c.resolvers[name] = resolver } -// LoadFromFile loads configuration from a YAML file at the specified path. -// DEPRECATED: Use NewFromConfigPath instead for cleaner API. -func (c *Config) LoadFromFile(path string) error { - file, err := os.Open(path) - if err != nil { - return fmt.Errorf("failed to open config file: %w", err) - } - defer func() { - _ = file.Close() - }() - - return c.LoadFromReader(file) -} - -// LoadFromReader loads configuration from an io.Reader containing YAML data. -// DEPRECATED: Use NewFromReader instead for cleaner API. -func (c *Config) LoadFromReader(reader io.Reader) error { - data, err := io.ReadAll(reader) - if err != nil { - return fmt.Errorf("failed to read config: %w", err) - } - - // Parse YAML first - if err := yaml.Unmarshal(data, &c.data); err != nil { - return fmt.Errorf("failed to parse YAML: %w", err) - } - - // Walk and interpolate the parsed structure - interpolated, err := c.walkAndInterpolate(c.data) - if err != nil { - return fmt.Errorf("failed to interpolate config: %w", err) - } - c.data = interpolated.(map[string]interface{}) - - // Handle environment variable injection - if err := c.injectEnvironment(); err != nil { - return fmt.Errorf("failed to inject environment variables: %w", err) - } - - return nil -} - func (c *Config) injectEnvironment() error { envData, ok := c.data["env"] if !ok { diff --git a/smartconfig_test.go b/smartconfig_test.go index 6a7f101..4e4fc08 100644 --- a/smartconfig_test.go +++ b/smartconfig_test.go @@ -76,9 +76,8 @@ func TestInterpolateBasic(t *testing.T) { _ = os.Setenv("BASIC", "value") defer func() { _ = os.Unsetenv("BASIC") }() - config := New() content := "test: ${ENV:BASIC}" - err := config.LoadFromReader(strings.NewReader(content)) + config, err := NewFromReader(strings.NewReader(content)) if err != nil { t.Fatalf("Failed to load config: %v", err) } @@ -198,9 +197,8 @@ func TestSimpleNestedInterpolation(t *testing.T) { _ = os.Unsetenv("FOO_BAR") }() - config := New() content := "test: ${ENV:FOO_${ENV:SUFFIX}}" - err := config.LoadFromReader(strings.NewReader(content)) + config, err := NewFromReader(strings.NewReader(content)) if err != nil { t.Fatalf("Failed to load config: %v", err) } @@ -227,8 +225,7 @@ func TestConfigInterpolation(t *testing.T) { _ = os.Unsetenv("NESTED_VALUE") }() - config := New() - err := config.LoadFromFile("./test/config.yaml") + config, err := NewFromConfigPath("./test/config.yaml") if err != nil { t.Fatalf("Failed to load config: %v", err) } @@ -286,8 +283,6 @@ func TestConfigInterpolation(t *testing.T) { } func TestRecursionLimit(t *testing.T) { - config := New() - // Create a deeply nested interpolation that hits the recursion limit // This has 4 levels, but our max is 3 content := "value: ${ENV:LEVEL1_${ENV:LEVEL2_${ENV:LEVEL3_${ENV:LEVEL4}}}}" @@ -311,7 +306,7 @@ func TestRecursionLimit(t *testing.T) { _ = os.Unsetenv("LEVEL1_depth2value") }() - err := config.LoadFromReader(strings.NewReader(content)) + config, err := NewFromReader(strings.NewReader(content)) if err != nil { t.Fatalf("Failed to load config: %v", err) } @@ -346,11 +341,10 @@ func TestInvalidResolverFormat(t *testing.T) { } func TestUnknownResolver(t *testing.T) { - config := New() content := "value: ${UNKNOWN:test}" // Unknown resolver should cause an error - err := config.LoadFromReader(strings.NewReader(content)) + _, err := NewFromReader(strings.NewReader(content)) if err == nil { t.Fatal("Expected error for unknown resolver, but got none") } diff --git a/version.go b/version.go new file mode 100644 index 0000000..753807c --- /dev/null +++ b/version.go @@ -0,0 +1,7 @@ +package smartconfig + +// Version is the current version of smartconfig +const Version = "1.0.0" + +// GitCommit is the git commit hash (set at build time with -ldflags) +var GitCommit = "unknown"