Enhance Configuration System with Environment Variable Priority Handling
- Updated the configuration loading mechanism to prioritize OS environment variables over .env file values, improving flexibility and security. - Added a new function, GetEnvWithPriority, to explicitly check the source of configuration values. - Revised documentation to reflect the new priority system, including detailed examples and usage instructions for mixed configuration approaches. - Enhanced error handling for .env file loading, ensuring graceful handling of missing files without disrupting the application flow.
This commit is contained in:
+141
-11
@@ -1,6 +1,14 @@
|
||||
# Environment-Based Configuration System
|
||||
|
||||
This package provides a configuration system for the Tender Management backend that loads configuration exclusively from `.env` files. It maintains the same structure as the previous YAML-based system while using environment variables for all configuration values.
|
||||
This package provides a configuration system for the Tender Management backend that loads configuration from both OS environment variables and `.env` files, with **OS environment variables taking priority** over `.env` file values. It maintains the same structure as the previous YAML-based system while using environment variables for all configuration values.
|
||||
|
||||
## Priority System
|
||||
|
||||
The configuration loader follows this priority order:
|
||||
1. **OS Environment Variables** (highest priority) - System-level environment variables
|
||||
2. **`.env` File Values** (lower priority) - File-based configuration for development
|
||||
|
||||
This means that if you set `SERVER_PORT=8080` in your `.env` file but have `SERVER_PORT=9000` as an OS environment variable, the system will use `9000`.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -63,7 +71,9 @@ func initConfig() Config {
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create .env Configuration File
|
||||
### 3. Configuration Sources
|
||||
|
||||
#### Option A: Using .env File Only (Development)
|
||||
|
||||
Create a `.env` file in your command directory:
|
||||
|
||||
@@ -104,6 +114,82 @@ SOME_TIMEOUT=30s
|
||||
SOME_NUMBER=42
|
||||
```
|
||||
|
||||
#### Option B: Using OS Environment Variables (Production)
|
||||
|
||||
```bash
|
||||
# Set environment variables directly
|
||||
export SERVER_HOST=0.0.0.0
|
||||
export SERVER_PORT=8080
|
||||
export MONGODB_URI=mongodb://mongo:27017
|
||||
export MONGODB_NAME=production_db
|
||||
export LOG_LEVEL=error
|
||||
|
||||
# Run your application
|
||||
./your-command
|
||||
```
|
||||
|
||||
#### Option C: Mixed Approach (Recommended)
|
||||
|
||||
1. Create a `.env` file with development defaults
|
||||
2. Override specific values with OS environment variables for production:
|
||||
|
||||
```bash
|
||||
# .env file contains development defaults
|
||||
# Override critical values with OS env vars
|
||||
export MONGODB_URI=mongodb://production-server:27017
|
||||
export MONGODB_NAME=production_db
|
||||
export LOG_LEVEL=error
|
||||
|
||||
# Run application - will use .env defaults except for overridden values
|
||||
./your-command
|
||||
```
|
||||
|
||||
## Priority Example
|
||||
|
||||
Given this `.env` file:
|
||||
```env
|
||||
SERVER_HOST=localhost
|
||||
SERVER_PORT=8080
|
||||
LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
And these OS environment variables:
|
||||
```bash
|
||||
export SERVER_PORT=9000
|
||||
export LOG_LEVEL=error
|
||||
```
|
||||
|
||||
The final configuration will be:
|
||||
- `SERVER_HOST=localhost` (from .env file, no OS override)
|
||||
- `SERVER_PORT=9000` (OS environment variable overrides .env)
|
||||
- `LOG_LEVEL=error` (OS environment variable overrides .env)
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Checking Configuration Source
|
||||
|
||||
You can check whether a configuration value came from OS environment or .env file:
|
||||
|
||||
```go
|
||||
import "tm/pkg/config"
|
||||
|
||||
func main() {
|
||||
value, isFromOS := config.GetEnvWithPriority("SERVER_PORT")
|
||||
if isFromOS {
|
||||
fmt.Printf("SERVER_PORT=%s (from OS environment)\n", value)
|
||||
} else {
|
||||
fmt.Printf("SERVER_PORT=%s (from .env file)\n", value)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Graceful .env File Handling
|
||||
|
||||
The system gracefully handles missing `.env` files:
|
||||
- If `.env` file exists: loads values as defaults
|
||||
- If `.env` file is missing: continues with OS environment variables only
|
||||
- No errors are thrown for missing `.env` files
|
||||
|
||||
## Examples
|
||||
|
||||
### Web Command Config
|
||||
@@ -164,9 +250,11 @@ type ScraperConfig struct {
|
||||
|
||||
The configuration loader uses Go generics to provide type-safe configuration loading. The function signature ensures that you get back exactly the type you expect.
|
||||
|
||||
### Environment Variable Support
|
||||
### Environment Variable Priority
|
||||
|
||||
The configuration system loads all values from `.env` files using the `github.com/subosito/gotenv` library.
|
||||
The configuration system loads values with explicit priority:
|
||||
1. OS environment variables (system-level, highest priority)
|
||||
2. .env file values (file-based, development defaults)
|
||||
|
||||
### Reflection-Based Parsing
|
||||
|
||||
@@ -193,7 +281,11 @@ The configuration system supports the following Go types:
|
||||
|
||||
5. **Use appropriate types**: Use `time.Duration` for durations, proper numeric types for numbers, etc.
|
||||
|
||||
6. **Provide sensible defaults**: Consider providing sensible defaults in your code when environment variables are not set.
|
||||
6. **Provide sensible defaults in .env**: Use `.env` files to provide development defaults and override with OS environment variables for production.
|
||||
|
||||
7. **Leverage the priority system**: Use `.env` for development defaults and OS environment variables for production overrides.
|
||||
|
||||
8. **Keep secrets in OS environment variables**: Never put sensitive values like API keys or passwords in `.env` files; use OS environment variables instead.
|
||||
|
||||
## Migration from YAML System
|
||||
|
||||
@@ -223,7 +315,7 @@ type Config struct {
|
||||
// Old way (YAML loading)
|
||||
conf, err := config.LoadConfig(".", &Config{})
|
||||
|
||||
// New way (.env loading)
|
||||
// New way (.env loading) - same API!
|
||||
conf, err := config.LoadConfig(".", &Config{})
|
||||
```
|
||||
|
||||
@@ -240,13 +332,51 @@ The system follows these naming conventions:
|
||||
## Error Handling
|
||||
|
||||
The configuration system provides detailed error messages when:
|
||||
- The `.env` file cannot be loaded
|
||||
- The `.env` file exists but cannot be loaded
|
||||
- Environment variables cannot be parsed into the expected types
|
||||
- Required fields are missing (handled by your application logic)
|
||||
|
||||
Note: Missing `.env` files do not cause errors - the system will continue with OS environment variables only.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Never commit `.env` files**: Add `.env` to your `.gitignore` file
|
||||
2. **Use environment-specific files**: Create different `.env` files for different environments (`.env.local`, `.env.production`, etc.)
|
||||
3. **Validate sensitive values**: Always validate sensitive configuration values like API keys and database URIs
|
||||
4. **Use secure defaults**: Provide secure default values for security-related configuration
|
||||
1. **Never commit `.env` files with secrets**: Add `.env` to your `.gitignore` file and never include sensitive values
|
||||
2. **Use OS environment variables for production secrets**: Set sensitive values like API keys and database passwords as OS environment variables
|
||||
3. **Use environment-specific files**: Create different `.env` files for different environments (`.env.local`, `.env.development`, etc.)
|
||||
4. **Validate sensitive values**: Always validate sensitive configuration values like API keys and database URIs
|
||||
5. **Use secure defaults**: Provide secure default values for security-related configuration
|
||||
6. **Leverage priority system for security**: Keep development defaults in `.env` and override with secure OS environment variables in production
|
||||
|
||||
## Deployment Strategies
|
||||
|
||||
### Development
|
||||
```bash
|
||||
# Use .env file for all configuration
|
||||
./your-command
|
||||
```
|
||||
|
||||
### Staging
|
||||
```bash
|
||||
# Use .env for defaults, override key values
|
||||
export MONGODB_URI=mongodb://staging-db:27017
|
||||
export LOG_LEVEL=warn
|
||||
./your-command
|
||||
```
|
||||
|
||||
### Production
|
||||
```bash
|
||||
# Override all critical values with OS environment variables
|
||||
export MONGODB_URI=mongodb://prod-cluster:27017
|
||||
export MONGODB_NAME=production
|
||||
export LOG_LEVEL=error
|
||||
export SERVER_HOST=0.0.0.0
|
||||
./your-command
|
||||
```
|
||||
|
||||
### Docker
|
||||
```dockerfile
|
||||
# Set production values in Dockerfile or docker-compose
|
||||
ENV MONGODB_URI=mongodb://mongo:27017
|
||||
ENV LOG_LEVEL=info
|
||||
ENV SERVER_HOST=0.0.0.0
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user