Add configuration management with Viper
- Implement pkg/config package with Viper integration - Support environment variables with DLC_ prefix - Add configurable server host, port, and shutdown timeout - Update server to use configuration values - Add config.example.yaml template file - Update AGENTS.md with configuration documentation - Maintain backward compatibility with default values Configuration options: - DLC_SERVER_HOST (default: 0.0.0.0) - DLC_SERVER_PORT (default: 8080) - DLC_SHUTDOWN_TIMEOUT (default: 30s)
This commit is contained in:
70
AGENTS.md
70
AGENTS.md
@@ -38,6 +38,14 @@ This file documents the AI agents, tools, and development workflow for the Dance
|
||||
- Server management guide
|
||||
- API endpoint documentation
|
||||
|
||||
### Phase 5: Configuration Management (Completed ✅)
|
||||
- Viper integration for configuration
|
||||
- Environment variable support with DLC_ prefix
|
||||
- Customizable server host/port
|
||||
- Configurable shutdown timeout
|
||||
- Configuration validation and logging
|
||||
- Example configuration file
|
||||
|
||||
## 🛠️ Tools & Technologies
|
||||
|
||||
| Component | Technology | Version |
|
||||
@@ -45,6 +53,7 @@ This file documents the AI agents, tools, and development workflow for the Dance
|
||||
| Language | Go | 1.26.1 |
|
||||
| Router | Chi | v5.2.5 |
|
||||
| Logging | Zerolog | v1.35.0 |
|
||||
| Configuration | Viper | v1.21.0 |
|
||||
| Testing | Standard Library | - |
|
||||
| Dependency Management | Go Modules | - |
|
||||
|
||||
@@ -58,6 +67,8 @@ DanceLessonsCoach/
|
||||
│ └── server/ # Web server
|
||||
│ └── main.go
|
||||
├── pkg/
|
||||
│ ├── config/ # Configuration management
|
||||
│ │ └── config.go # Viper-based config
|
||||
│ ├── greet/ # Core domain logic
|
||||
│ │ ├── api_v1.go # API handlers
|
||||
│ │ ├── greet.go # Service implementation
|
||||
@@ -66,6 +77,7 @@ DanceLessonsCoach/
|
||||
│ └── server.go
|
||||
├── go.mod # Dependencies
|
||||
├── go.sum # Dependency checksums
|
||||
├── config.example.yaml # Configuration template
|
||||
├── README.md # User documentation
|
||||
├── AGENTS.md # This file
|
||||
└── .gitignore # Ignore patterns
|
||||
@@ -98,6 +110,64 @@ Server running on :8080
|
||||
- 30-second shutdown timeout
|
||||
- Proper resource cleanup
|
||||
|
||||
### Configuration Management
|
||||
|
||||
The server supports flexible configuration through environment variables with the `DLC_` prefix:
|
||||
|
||||
**Available Configuration Options:**
|
||||
|
||||
| Option | Environment Variable | Default Value | Description |
|
||||
|--------|---------------------|---------------|-------------|
|
||||
| Host | `DLC_SERVER_HOST` | `0.0.0.0` | Server bind address |
|
||||
| Port | `DLC_SERVER_PORT` | `8080` | Server listening port |
|
||||
| Shutdown Timeout | `DLC_SHUTDOWN_TIMEOUT` | `30s` | Graceful shutdown timeout |
|
||||
|
||||
**Usage Examples:**
|
||||
|
||||
```bash
|
||||
# Custom port
|
||||
export DLC_SERVER_PORT=9090
|
||||
go run cmd/server/main.go &
|
||||
|
||||
# Custom host and port
|
||||
export DLC_SERVER_HOST="127.0.0.1"
|
||||
export DLC_SERVER_PORT=8081
|
||||
go run cmd/server/main.go &
|
||||
|
||||
# Custom shutdown timeout
|
||||
export DLC_SHUTDOWN_TIMEOUT=45s
|
||||
go run cmd/server/main.go &
|
||||
```
|
||||
|
||||
**Configuration File Support:**
|
||||
|
||||
A `config.example.yaml` file is provided as a template:
|
||||
|
||||
```yaml
|
||||
server:
|
||||
host: "0.0.0.0"
|
||||
port: 8080
|
||||
|
||||
shutdown:
|
||||
timeout: 30s
|
||||
```
|
||||
|
||||
**Configuration Loading:**
|
||||
- Environment variables take precedence over defaults
|
||||
- All configuration is validated on startup
|
||||
- Invalid configurations cause server startup failure
|
||||
- Configuration values are logged at startup
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Test with custom configuration
|
||||
DLC_SERVER_PORT=9090 DLC_SERVER_HOST="127.0.0.1" go run cmd/server/main.go
|
||||
|
||||
# Verify it's running on the custom port
|
||||
curl http://127.0.0.1:9090/api/health
|
||||
# Expected: {"status":"healthy"}
|
||||
```
|
||||
|
||||
### Checking Server Status
|
||||
|
||||
```bash
|
||||
|
||||
@@ -7,13 +7,19 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"DanceLessonsCoach/pkg/config"
|
||||
"DanceLessonsCoach/pkg/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Load configuration
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to load configuration")
|
||||
}
|
||||
|
||||
// Create root context with cancellation
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -27,11 +33,11 @@ func main() {
|
||||
serverCtx, serverStop := context.WithCancel(ctx)
|
||||
|
||||
go func() {
|
||||
fmt.Println("Server running on :8080")
|
||||
log.Info().Msg("Starting HTTP server on :8080")
|
||||
fmt.Printf("Server running on %s\n", cfg.GetServerAddress())
|
||||
log.Info().Str("address", cfg.GetServerAddress()).Msg("Starting HTTP server")
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":8080",
|
||||
Addr: cfg.GetServerAddress(),
|
||||
Handler: server.Router(),
|
||||
}
|
||||
|
||||
@@ -40,8 +46,8 @@ func main() {
|
||||
<-sigChan
|
||||
log.Info().Msg("Shutdown signal received")
|
||||
|
||||
// Create shutdown context with timeout
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
// Create shutdown context with timeout from config
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), cfg.Shutdown.Timeout)
|
||||
defer shutdownCancel()
|
||||
|
||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
|
||||
23
config.example.yaml
Normal file
23
config.example.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
# DanceLessonsCoach Configuration Example
|
||||
# This file shows the available configuration options
|
||||
# You can use this as a template for your own configuration
|
||||
|
||||
# Server configuration
|
||||
server:
|
||||
# Host address to bind to (default: "0.0.0.0")
|
||||
host: "0.0.0.0"
|
||||
|
||||
# Port to listen on (default: 8080)
|
||||
port: 8080
|
||||
|
||||
# Shutdown configuration
|
||||
shutdown:
|
||||
# Timeout duration for graceful shutdown (default: 30s)
|
||||
# Format: number + unit (s, m, h)
|
||||
timeout: 30s
|
||||
|
||||
# Environment Variables
|
||||
# You can also configure via environment variables with DLC_ prefix:
|
||||
# DLC_SERVER_HOST=0.0.0.0
|
||||
# DLC_SERVER_PORT=8080
|
||||
# DLC_SHUTDOWN_TIMEOUT=30s
|
||||
12
go.mod
12
go.mod
@@ -3,9 +3,21 @@ module DanceLessonsCoach
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.5 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/rs/zerolog v1.35.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
)
|
||||
|
||||
25
go.sum
25
go.sum
@@ -1,11 +1,36 @@
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
|
||||
github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
57
pkg/config/config.go
Normal file
57
pkg/config/config.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config represents the application configuration
|
||||
type Config struct {
|
||||
Server struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
Shutdown struct {
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
}
|
||||
}
|
||||
|
||||
// LoadConfig loads configuration from environment variables and defaults
|
||||
func LoadConfig() (*Config, error) {
|
||||
v := viper.New()
|
||||
|
||||
// Set default values
|
||||
v.SetDefault("server.host", "0.0.0.0")
|
||||
v.SetDefault("server.port", 8080)
|
||||
v.SetDefault("shutdown.timeout", 30*time.Second)
|
||||
|
||||
// Bind environment variables
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvPrefix("DLC") // DanceLessonsCoach prefix
|
||||
v.BindEnv("server.host", "DLC_SERVER_HOST")
|
||||
v.BindEnv("server.port", "DLC_SERVER_PORT")
|
||||
v.BindEnv("shutdown.timeout", "DLC_SHUTDOWN_TIMEOUT")
|
||||
|
||||
// Unmarshal into Config struct
|
||||
var config Config
|
||||
if err := v.Unmarshal(&config); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to unmarshal config")
|
||||
return nil, fmt.Errorf("config unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("host", config.Server.Host).
|
||||
Int("port", config.Server.Port).
|
||||
Dur("shutdown_timeout", config.Shutdown.Timeout).
|
||||
Msg("Configuration loaded")
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// GetServerAddress returns the formatted server address (host:port)
|
||||
func (c *Config) GetServerAddress() string {
|
||||
return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
|
||||
}
|
||||
Reference in New Issue
Block a user