re-uploading work
This commit is contained in:
553
docs/configuration/env-to-yaml-migration.md
Normal file
553
docs/configuration/env-to-yaml-migration.md
Normal file
@@ -0,0 +1,553 @@
|
||||
# Migration Guide: .env to YAML Configuration
|
||||
|
||||
This guide helps you migrate from `.env` file configuration to the new YAML-based configuration system.
|
||||
|
||||
## Why Migrate to YAML?
|
||||
|
||||
The YAML configuration format offers several advantages:
|
||||
|
||||
- **Better readability**: Clear hierarchical structure
|
||||
- **Comments**: Document your configuration inline
|
||||
- **Complex structures**: Native support for arrays, nested objects
|
||||
- **Type safety**: Automatic type conversion
|
||||
- **Environment-specific configs**: Easy to maintain separate configs per environment
|
||||
- **No prefix noise**: No need for `ATTUNE__` prefixes everywhere
|
||||
|
||||
## Quick Migration Steps
|
||||
|
||||
1. **Copy the example config**
|
||||
```bash
|
||||
cp config.example.yaml config.yaml
|
||||
```
|
||||
|
||||
2. **Convert your .env values** (see conversion examples below)
|
||||
|
||||
3. **Test the new configuration**
|
||||
```bash
|
||||
cargo run --bin attune-api
|
||||
```
|
||||
|
||||
4. **Remove old .env file** (optional, after verification)
|
||||
|
||||
## Conversion Reference
|
||||
|
||||
### Database Configuration
|
||||
|
||||
**Before (.env):**
|
||||
```bash
|
||||
ATTUNE__DATABASE__URL=postgresql://postgres:postgres@localhost:5432/attune
|
||||
ATTUNE__DATABASE__MAX_CONNECTIONS=50
|
||||
ATTUNE__DATABASE__MIN_CONNECTIONS=5
|
||||
ATTUNE__DATABASE__CONNECT_TIMEOUT=30
|
||||
ATTUNE__DATABASE__IDLE_TIMEOUT=600
|
||||
ATTUNE__DATABASE__LOG_STATEMENTS=true
|
||||
```
|
||||
|
||||
**After (config.yaml):**
|
||||
```yaml
|
||||
database:
|
||||
url: postgresql://postgres:postgres@localhost:5432/attune
|
||||
max_connections: 50
|
||||
min_connections: 5
|
||||
connect_timeout: 30
|
||||
idle_timeout: 600
|
||||
log_statements: true
|
||||
```
|
||||
|
||||
### Server Configuration
|
||||
|
||||
**Before (.env):**
|
||||
```bash
|
||||
ATTUNE__SERVER__HOST=0.0.0.0
|
||||
ATTUNE__SERVER__PORT=8080
|
||||
ATTUNE__SERVER__REQUEST_TIMEOUT=30
|
||||
ATTUNE__SERVER__ENABLE_CORS=true
|
||||
ATTUNE__SERVER__CORS_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||
ATTUNE__SERVER__MAX_BODY_SIZE=10485760
|
||||
```
|
||||
|
||||
**After (config.yaml):**
|
||||
```yaml
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8080
|
||||
request_timeout: 30
|
||||
enable_cors: true
|
||||
cors_origins:
|
||||
- http://localhost:3000
|
||||
- http://localhost:5173
|
||||
max_body_size: 10485760
|
||||
```
|
||||
|
||||
### Security Configuration
|
||||
|
||||
**Before (.env):**
|
||||
```bash
|
||||
ATTUNE__SECURITY__JWT_SECRET=your-secret-key-here
|
||||
ATTUNE__SECURITY__JWT_ACCESS_EXPIRATION=3600
|
||||
ATTUNE__SECURITY__JWT_REFRESH_EXPIRATION=604800
|
||||
ATTUNE__SECURITY__ENCRYPTION_KEY=your-32-char-encryption-key
|
||||
ATTUNE__SECURITY__ENABLE_AUTH=true
|
||||
```
|
||||
|
||||
**After (config.yaml):**
|
||||
```yaml
|
||||
security:
|
||||
jwt_secret: your-secret-key-here
|
||||
jwt_access_expiration: 3600
|
||||
jwt_refresh_expiration: 604800
|
||||
encryption_key: your-32-char-encryption-key
|
||||
enable_auth: true
|
||||
```
|
||||
|
||||
### Logging Configuration
|
||||
|
||||
**Before (.env):**
|
||||
```bash
|
||||
ATTUNE__LOG__LEVEL=info
|
||||
ATTUNE__LOG__FORMAT=json
|
||||
ATTUNE__LOG__CONSOLE=true
|
||||
ATTUNE__LOG__FILE=/var/log/attune/attune.log
|
||||
```
|
||||
|
||||
**After (config.yaml):**
|
||||
```yaml
|
||||
log:
|
||||
level: info
|
||||
format: json
|
||||
console: true
|
||||
file: /var/log/attune/attune.log
|
||||
```
|
||||
|
||||
### Redis Configuration
|
||||
|
||||
**Before (.env):**
|
||||
```bash
|
||||
ATTUNE__REDIS__URL=redis://localhost:6379
|
||||
ATTUNE__REDIS__POOL_SIZE=10
|
||||
```
|
||||
|
||||
**After (config.yaml):**
|
||||
```yaml
|
||||
redis:
|
||||
url: redis://localhost:6379
|
||||
pool_size: 10
|
||||
```
|
||||
|
||||
### Message Queue Configuration
|
||||
|
||||
**Before (.env):**
|
||||
```bash
|
||||
ATTUNE__MESSAGE_QUEUE__URL=amqp://guest:guest@localhost:5672/%2f
|
||||
ATTUNE__MESSAGE_QUEUE__EXCHANGE=attune
|
||||
ATTUNE__MESSAGE_QUEUE__ENABLE_DLQ=true
|
||||
ATTUNE__MESSAGE_QUEUE__MESSAGE_TTL=3600
|
||||
```
|
||||
|
||||
**After (config.yaml):**
|
||||
```yaml
|
||||
message_queue:
|
||||
url: amqp://guest:guest@localhost:5672/%2f
|
||||
exchange: attune
|
||||
enable_dlq: true
|
||||
message_ttl: 3600
|
||||
```
|
||||
|
||||
## Automated Conversion Script
|
||||
|
||||
If you have a complex `.env` file, you can use this Python script to help convert it:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Convert .env file to YAML configuration
|
||||
Usage: python env_to_yaml.py .env > config.yaml
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
def parse_env_line(line):
|
||||
"""Parse a single .env line"""
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
return None, None
|
||||
|
||||
match = re.match(r'ATTUNE__(.+?)=(.+)', line)
|
||||
if not match:
|
||||
return None, None
|
||||
|
||||
key = match.group(1)
|
||||
value = match.group(2)
|
||||
|
||||
# Remove quotes if present
|
||||
value = value.strip('"').strip("'")
|
||||
|
||||
# Convert boolean strings
|
||||
if value.lower() in ('true', 'false'):
|
||||
value = value.lower()
|
||||
# Try to convert to number
|
||||
elif value.isdigit():
|
||||
value = int(value)
|
||||
|
||||
return key, value
|
||||
|
||||
def build_yaml_structure(env_vars):
|
||||
"""Build nested YAML structure from flat env vars"""
|
||||
structure = {}
|
||||
|
||||
for key, value in env_vars.items():
|
||||
parts = key.lower().split('__')
|
||||
|
||||
# Navigate/create nested structure
|
||||
current = structure
|
||||
for part in parts[:-1]:
|
||||
if part not in current:
|
||||
current[part] = {}
|
||||
current = current[part]
|
||||
|
||||
# Handle special cases (arrays)
|
||||
final_key = parts[-1]
|
||||
if isinstance(value, str) and ',' in value:
|
||||
# Assume comma-separated list
|
||||
current[final_key] = [v.strip() for v in value.split(',')]
|
||||
else:
|
||||
current[final_key] = value
|
||||
|
||||
return structure
|
||||
|
||||
def print_yaml(data, indent=0):
|
||||
"""Print YAML structure"""
|
||||
for key, value in data.items():
|
||||
if isinstance(value, dict):
|
||||
print(f"{' ' * indent}{key}:")
|
||||
print_yaml(value, indent + 1)
|
||||
elif isinstance(value, list):
|
||||
print(f"{' ' * indent}{key}:")
|
||||
for item in value:
|
||||
print(f"{' ' * (indent + 1)}- {item}")
|
||||
elif isinstance(value, bool):
|
||||
print(f"{' ' * indent}{key}: {str(value).lower()}")
|
||||
elif isinstance(value, (int, float)):
|
||||
print(f"{' ' * indent}{key}: {value}")
|
||||
else:
|
||||
# Quote strings that might be ambiguous
|
||||
if any(c in str(value) for c in [':', '#', '@', '%']):
|
||||
print(f"{' ' * indent}{key}: \"{value}\"")
|
||||
else:
|
||||
print(f"{' ' * indent}{key}: {value}")
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python env_to_yaml.py <.env file>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
env_file = sys.argv[1]
|
||||
env_vars = {}
|
||||
|
||||
# Parse .env file
|
||||
with open(env_file, 'r') as f:
|
||||
for line in f:
|
||||
key, value = parse_env_line(line)
|
||||
if key and value is not None:
|
||||
env_vars[key] = value
|
||||
|
||||
# Build and print YAML structure
|
||||
structure = build_yaml_structure(env_vars)
|
||||
|
||||
print("# Attune Configuration")
|
||||
print("# Generated from .env file")
|
||||
print("# Please review and adjust as needed")
|
||||
print()
|
||||
|
||||
print_yaml(structure)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
python env_to_yaml.py .env > config.yaml
|
||||
```
|
||||
|
||||
**Note:** Always review the generated YAML file and adjust as needed.
|
||||
|
||||
## Handling Environment-Specific Configurations
|
||||
|
||||
One of the benefits of YAML is easy environment-specific configurations.
|
||||
|
||||
### Create Base Configuration
|
||||
|
||||
`config.yaml` (common settings):
|
||||
```yaml
|
||||
service_name: attune
|
||||
|
||||
database:
|
||||
max_connections: 50
|
||||
connect_timeout: 30
|
||||
|
||||
server:
|
||||
enable_cors: true
|
||||
max_body_size: 10485760
|
||||
|
||||
log:
|
||||
console: true
|
||||
```
|
||||
|
||||
### Development Override
|
||||
|
||||
`config.development.yaml`:
|
||||
```yaml
|
||||
environment: development
|
||||
|
||||
database:
|
||||
url: postgresql://postgres:postgres@localhost:5432/attune
|
||||
log_statements: true
|
||||
|
||||
server:
|
||||
host: 127.0.0.1
|
||||
port: 8080
|
||||
|
||||
log:
|
||||
level: debug
|
||||
format: pretty
|
||||
|
||||
security:
|
||||
jwt_secret: dev-secret-not-for-production
|
||||
jwt_access_expiration: 86400 # 24 hours
|
||||
```
|
||||
|
||||
### Production Configuration
|
||||
|
||||
`config.production.yaml`:
|
||||
```yaml
|
||||
environment: production
|
||||
|
||||
database:
|
||||
url: postgresql://user:password@db.prod.example.com/attune
|
||||
max_connections: 100
|
||||
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8080
|
||||
cors_origins:
|
||||
- https://app.example.com
|
||||
- https://www.example.com
|
||||
|
||||
log:
|
||||
level: info
|
||||
format: json
|
||||
file: /var/log/attune/attune.log
|
||||
|
||||
# Use environment variables for secrets!
|
||||
# ATTUNE__SECURITY__JWT_SECRET
|
||||
# ATTUNE__SECURITY__ENCRYPTION_KEY
|
||||
```
|
||||
|
||||
## Still Using Environment Variables
|
||||
|
||||
Environment variables still work for overrides! This is especially useful for:
|
||||
|
||||
- **Container deployments** (Docker, Kubernetes)
|
||||
- **Production secrets** (don't commit secrets to YAML files)
|
||||
- **CI/CD pipelines**
|
||||
- **Quick overrides** without editing files
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Base config in config.yaml, override database URL
|
||||
export ATTUNE__DATABASE__URL=postgresql://prod-db/attune
|
||||
cargo run --bin attune-api
|
||||
```
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
- [ ] Back up your existing `.env` file
|
||||
- [ ] Copy `config.example.yaml` to `config.yaml`
|
||||
- [ ] Convert `.env` values to YAML format
|
||||
- [ ] Add comments to document your configuration
|
||||
- [ ] Test the application starts successfully
|
||||
- [ ] Verify all features work (database, authentication, etc.)
|
||||
- [ ] Create environment-specific configs if needed
|
||||
- [ ] Update deployment scripts/documentation
|
||||
- [ ] Set up environment variables for production secrets
|
||||
- [ ] Add `config.yaml` to `.gitignore` (if it contains secrets)
|
||||
- [ ] Commit example configs to version control
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If you need to roll back to `.env` files:
|
||||
|
||||
1. The old `.env` file still works (keep your backup)
|
||||
2. Rename/remove `config.yaml` temporarily
|
||||
3. Environment variables with `ATTUNE__` prefix still work
|
||||
|
||||
**Note:** The application will still load config from environment variables if no YAML file is present.
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Issue: Configuration not loading
|
||||
|
||||
**Problem:** Application can't find `config.yaml`
|
||||
|
||||
**Solution:**
|
||||
- Ensure `config.yaml` is in the current working directory
|
||||
- Or set `ATTUNE_CONFIG` environment variable:
|
||||
```bash
|
||||
export ATTUNE_CONFIG=/path/to/config.yaml
|
||||
```
|
||||
|
||||
### Issue: YAML syntax error
|
||||
|
||||
**Problem:** YAML parsing fails
|
||||
|
||||
**Solution:**
|
||||
- Check indentation (use spaces, not tabs)
|
||||
- Validate YAML syntax:
|
||||
```bash
|
||||
# Using Python
|
||||
python -c "import yaml; yaml.safe_load(open('config.yaml'))"
|
||||
|
||||
# Using yq
|
||||
yq eval config.yaml
|
||||
```
|
||||
|
||||
### Issue: Boolean values not working
|
||||
|
||||
**Problem:** Boolean value treated as string
|
||||
|
||||
**Solution:** Use lowercase without quotes:
|
||||
```yaml
|
||||
# Correct
|
||||
enable_auth: true
|
||||
log_statements: false
|
||||
|
||||
# Incorrect
|
||||
enable_auth: "true" # String, not boolean
|
||||
LOG_STATEMENTS: FALSE # Wrong case
|
||||
```
|
||||
|
||||
### Issue: Array values not parsing
|
||||
|
||||
**Problem:** CORS origins not loading correctly
|
||||
|
||||
**Solution:** Use YAML array syntax:
|
||||
```yaml
|
||||
# Correct
|
||||
cors_origins:
|
||||
- http://localhost:3000
|
||||
- http://localhost:5173
|
||||
|
||||
# Also correct (inline)
|
||||
cors_origins: [http://localhost:3000, http://localhost:5173]
|
||||
|
||||
# For env vars, use comma-separated
|
||||
export ATTUNE__SERVER__CORS_ORIGINS="http://localhost:3000,http://localhost:5173"
|
||||
```
|
||||
|
||||
### Issue: Special characters in strings
|
||||
|
||||
**Problem:** URL or path not parsing correctly
|
||||
|
||||
**Solution:** Quote strings with special characters:
|
||||
```yaml
|
||||
# With special characters
|
||||
database:
|
||||
url: "postgresql://user:p@ssw0rd!@localhost/attune"
|
||||
|
||||
# Or use single quotes
|
||||
redis:
|
||||
url: 'redis://localhost:6379'
|
||||
```
|
||||
|
||||
## Tips and Best Practices
|
||||
|
||||
### 1. Use Comments
|
||||
|
||||
Document your configuration:
|
||||
```yaml
|
||||
database:
|
||||
# Production database endpoint
|
||||
url: postgresql://user@db.example.com/attune
|
||||
|
||||
# Adjust based on expected load
|
||||
max_connections: 100 # Increased for production traffic
|
||||
```
|
||||
|
||||
### 2. Group Related Settings
|
||||
|
||||
Keep related configuration together:
|
||||
```yaml
|
||||
# All server-related settings
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8080
|
||||
cors_origins:
|
||||
- https://app.example.com
|
||||
```
|
||||
|
||||
### 3. Use Environment-Specific Files
|
||||
|
||||
Don't duplicate settings—use base + overrides:
|
||||
```
|
||||
config.yaml # Common settings
|
||||
config.development.yaml # Dev overrides
|
||||
config.production.yaml # Prod overrides
|
||||
```
|
||||
|
||||
### 4. Never Commit Secrets
|
||||
|
||||
Use environment variables for sensitive data:
|
||||
```yaml
|
||||
# config.production.yaml (committed to git)
|
||||
security:
|
||||
# Override these with environment variables!
|
||||
jwt_secret: CHANGE_ME_USE_ENV_VAR
|
||||
encryption_key: CHANGE_ME_USE_ENV_VAR
|
||||
```
|
||||
|
||||
```bash
|
||||
# In production (not in git)
|
||||
export ATTUNE__SECURITY__JWT_SECRET=$(openssl rand -base64 64)
|
||||
export ATTUNE__SECURITY__ENCRYPTION_KEY=$(openssl rand -base64 32)
|
||||
```
|
||||
|
||||
### 5. Validate Configuration
|
||||
|
||||
Always validate after migration:
|
||||
```bash
|
||||
# The application will validate on startup
|
||||
cargo run --bin attune-api
|
||||
|
||||
# Check for validation errors in logs
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues during migration:
|
||||
|
||||
1. **Check the logs**: Enable debug logging
|
||||
```bash
|
||||
ATTUNE__LOG__LEVEL=debug cargo run --bin attune-api
|
||||
```
|
||||
|
||||
2. **Review documentation**:
|
||||
- [Configuration Guide](configuration.md)
|
||||
- [Quick Start Guide](quick-start.md)
|
||||
|
||||
3. **Validate YAML syntax**: Use online validators or CLI tools
|
||||
|
||||
4. **Compare with examples**: Check `config.example.yaml`
|
||||
|
||||
## Summary
|
||||
|
||||
The migration from `.env` to YAML provides:
|
||||
|
||||
✅ Better readability and maintainability
|
||||
✅ Native support for complex data types
|
||||
✅ Easy environment-specific configurations
|
||||
✅ Inline documentation with comments
|
||||
✅ Backward compatibility with environment variables
|
||||
|
||||
The YAML configuration system gives you more flexibility while maintaining the security and override capabilities of environment variables.
|
||||
Reference in New Issue
Block a user