Add flexible configuration with custom file path support

\n- Add DLC_CONFIG_FILE environment variable for custom config paths\n- Update config.go to support both default and custom config file locations\n- Update README.md and AGENTS.md with configuration documentation\n- Remove /Users/gabrielradureau/.dancelessonscoach/ directory approach\n- Add os import to config.go\n- Test all configuration scenarios
This commit is contained in:
Gabriel Radureau
2026-04-03 15:50:18 +02:00
parent f50de95f05
commit eaaa63d74e
4 changed files with 142 additions and 10 deletions

View File

@@ -141,7 +141,15 @@ go run cmd/server/main.go &
**Configuration File Support:** **Configuration File Support:**
A `config.example.yaml` file is provided as a template: A `config.example.yaml` file is provided as a template. By default, the application looks for `config.yaml` in the current working directory.
To specify a custom config file path, set the `DLC_CONFIG_FILE` environment variable:
```bash
DLC_CONFIG_FILE="/path/to/config.yaml" go run ./cmd/server
```
Example `config.yaml`:
```yaml ```yaml
server: server:
@@ -153,10 +161,12 @@ shutdown:
``` ```
**Configuration Loading:** **Configuration Loading:**
- Environment variables take precedence over defaults - **File-based configuration** takes highest precedence
- **Environment variables** override defaults but are overridden by config file
- **Default values** are used when no other configuration is provided
- All configuration is validated on startup - All configuration is validated on startup
- Invalid configurations cause server startup failure - Invalid configurations cause server startup failure
- Configuration values are logged at startup - Configuration values and source are logged at startup
**Verification:** **Verification:**
```bash ```bash

104
README.md
View File

@@ -1,11 +1,16 @@
# DanceLessonsCoach # DanceLessonsCoach
A simple Go project demonstrating idiomatic package structure and CLI implementation. A Go project demonstrating idiomatic package structure, CLI implementation, and JSON API with Chi router.
## Features ## Features
- Greet function with default behavior - Greet function with default behavior
- Command-line interface - Command-line interface
- JSON API with versioned endpoints
- Chi router integration
- Zerolog for high-performance logging
- Viper for configuration management
- Graceful shutdown with context
- Unit tests - Unit tests
- Go 1.26.1 compatible - Go 1.26.1 compatible
@@ -20,8 +25,70 @@ cd DanceLessonsCoach
go run ./cmd/greet go run ./cmd/greet
``` ```
## Optional Configuration
The project supports configuration via YAML file, environment variables, or defaults.
Configuration priority: file > environment variables > defaults
### Configuration File
By default, the application looks for `config.yaml` in the current working directory.
Create a `config.yaml` file:
```yaml
server:
host: "0.0.0.0"
port: 8080
shutdown:
timeout: 30s
```
Then start the server:
```bash
go run ./cmd/server
```
### Custom Config File Path
To specify a custom config file path, use the `DLC_CONFIG_FILE` environment variable:
```bash
# Use a specific config file
export DLC_CONFIG_FILE="/path/to/your/config.yaml"
go run ./cmd/server
# Or in one command
DLC_CONFIG_FILE="/path/to/your/config.yaml" go run ./cmd/server
```
### Environment Variables
You can also configure via environment variables with `DLC_` prefix:
```bash
# Set configuration via environment variables
export DLC_SERVER_HOST="0.0.0.0"
export DLC_SERVER_PORT=8080
export DLC_SHUTDOWN_TIMEOUT=30s
# Start the server
go run ./cmd/server
```
### Configuration Priority
1. **File-based configuration** (highest priority)
2. **Environment variables** (override defaults)
3. **Default values** (lowest priority)
This means if you have both a config file and environment variables, the file takes precedence.
## Usage ## Usage
### CLI
```bash ```bash
# Default greeting # Default greeting
go run ./cmd/greet go run ./cmd/greet
@@ -32,6 +99,23 @@ go run ./cmd/greet John
# Output: Hello John! # Output: Hello John!
``` ```
### Web Server
```bash
# Start the server
go run ./cmd/server
# Test API endpoints
curl http://localhost:8080/api/health
# Output: {"status":"healthy"}
curl http://localhost:8080/api/v1/greet
# Output: {"message":"Hello world!"}
curl http://localhost:8080/api/v1/greet/John
# Output: {"message":"Hello John!"}
```
## Testing ## Testing
```bash ```bash
@@ -47,12 +131,20 @@ go test ./pkg/greet/
``` ```
DanceLessonsCoach/ DanceLessonsCoach/
├── cmd/ ├── cmd/
── greet/ ── greet/ # CLI entry point
└── main.go # CLI entry point └── main.go
│ └── server/ # Web server entry point
│ └── main.go
├── pkg/ ├── pkg/
── greet/ ── config/ # Configuration management
── greet.go # Core library ── config.go
── greet_test.go # Unit tests ── greet/ # Core greet functionality
│ │ ├── api_v1.go # API v1 handlers
│ │ ├── greet.go # Core service
│ │ └── greet_test.go # Unit tests
│ └── server/ # Server implementation
│ └── server.go
├── config.example.yaml # Configuration template
├── go.mod # Go module definition ├── go.mod # Go module definition
└── README.md # Project documentation └── README.md # Project documentation
``` ```

5
config.yaml Normal file
View File

@@ -0,0 +1,5 @@
server:
host: "0.0.0.0"
port: 8080
shutdown:
timeout: 5s

View File

@@ -2,6 +2,7 @@ package config
import ( import (
"fmt" "fmt"
"os"
"time" "time"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -19,7 +20,9 @@ type Config struct {
} }
} }
// LoadConfig loads configuration from environment variables and defaults // LoadConfig loads configuration from file, environment variables, and defaults
// Configuration priority: file > environment variables > defaults
// To specify a custom config file path, set DLC_CONFIG_FILE environment variable
func LoadConfig() (*Config, error) { func LoadConfig() (*Config, error) {
v := viper.New() v := viper.New()
@@ -28,6 +31,28 @@ func LoadConfig() (*Config, error) {
v.SetDefault("server.port", 8080) v.SetDefault("server.port", 8080)
v.SetDefault("shutdown.timeout", 30*time.Second) v.SetDefault("shutdown.timeout", 30*time.Second)
// Check for custom config file path via environment variable
if configFile := os.Getenv("DLC_CONFIG_FILE"); configFile != "" {
v.SetConfigFile(configFile)
log.Info().Str("config_file", configFile).Msg("Using custom config file path")
} else {
// Default: look for config.yaml in current directory
v.SetConfigName("config")
v.SetConfigType("yaml")
v.AddConfigPath(".")
}
// Read config file if it exists
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
// Config file was found but there was an error reading it
log.Warn().Err(err).Msg("Error reading config file, using defaults")
}
// Config file not found, continue with environment variables and defaults
} else {
log.Info().Str("config_file", v.ConfigFileUsed()).Msg("Config file loaded")
}
// Bind environment variables // Bind environment variables
v.AutomaticEnv() v.AutomaticEnv()
v.SetEnvPrefix("DLC") // DanceLessonsCoach prefix v.SetEnvPrefix("DLC") // DanceLessonsCoach prefix