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 <noreply@anthropic.com>
This commit is contained in:
Jeffrey Paul 2025-07-22 13:38:34 +02:00
parent 47801ce852
commit 82f09a16ec
6 changed files with 98 additions and 54 deletions

View File

@ -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)

74
TODO.md Normal file
View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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")
}

7
version.go Normal file
View File

@ -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"