Table of Contents
- EZConf - v0.1.1
- Installation
- Key Concepts and Features
- Unified Configuration Management
- Source Code Parsing
- .env File Management
- Environment Variable Documentation
- Quick Start
- Configuration
- Detailed Usage
- Using Built-in Integrations
- Creating Custom Integrations
- Loading Configurations
- Getting All Configurations
- Printing Environment Variables
- Writing to Custom Writer
- Generating .env Files
- Updating Existing .env Files
- Getting Environment Variable Metadata
- ENV Comment Format
- Basic Format
- With Default Value
- Required Variable
- Conditional Requirement
- Inline Comments
- Multiple Modifiers
- Integration
- Best Practices
- 1. Use Consistent Comment Format
- 2. Group Related Configurations
- 3. Use Descriptive Names
- 4. Document Requirements Clearly
- 5. Provide Sensible Defaults
- 6. Generate .env Templates
- Troubleshooting
- ENV Comments Not Being Parsed
- Config Function Fails to Load
- .env File Update Doesn't Work
- Missing Environment Variables in Output
- See Also
- Links
EZConf - v0.1.1
A unified configuration management system for loading and managing environment-based configurations across multiple Go packages.
Installation
go get git.haelnorr.com/h/golib/ezconf
Key Concepts and Features
Unified Configuration Management
EZConf provides a centralized way to manage configurations from multiple packages that follow the ConfigFromEnv pattern. Instead of calling each package's configuration function individually, you can register them all with EZConf and load them together.
Source Code Parsing
EZConf parses Go source files to extract environment variable documentation directly from struct field comments. This eliminates the need to maintain separate documentation for environment variables.
.env File Management
EZConf can generate new .env files or update existing ones, preserving existing values while adding new variables. This makes it easy to maintain environment configuration across different deployment environments.
Environment Variable Documentation
EZConf supports a standard comment format for documenting environment variables:
// ENV VAR_NAME: Description (modifiers)
Supported modifiers:
(required)- marks variable as required(required if condition)- marks variable as conditionally required(default: value)- specifies default value
Quick Start
Basic Usage with Built-in Integration (Recommended)
package main
import (
"log"
"os"
"git.haelnorr.com/h/golib/ezconf"
"git.haelnorr.com/h/golib/hlog"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/golib/hwsauth"
)
func main() {
// Create loader
loader := ezconf.New()
// Register packages using built-in integrations
loader.RegisterIntegrations(
hlog.NewEZConfIntegration(),
hws.NewEZConfIntegration(),
hwsauth.NewEZConfIntegration(),
)
// Parse the envvars from the config structs
if err := loader.ParseEnvVars(); err != nil {
log.Fatal(err)
}
// Load the configs
if err := loader.LoadConfigs(); err != nil {
log.Fatal(err)
}
// Get configuration
hlogCfg, _ := loader.GetConfig("hlog")
cfg := hlogCfg.(*hlog.Config)
// Use configuration
logger, _ := hlog.NewLogger(cfg, os.Stdout)
logger.Info().Msg("Application started")
}
Manual Registration
package main
import (
"log"
"os"
"git.haelnorr.com/h/golib/ezconf"
"git.haelnorr.com/h/golib/hlog"
)
func main() {
// Create loader
loader := ezconf.New()
// Add package path
loader.AddPackagePath("vendor/git.haelnorr.com/h/golib/hlog")
// Add config function
loader.AddConfigFunc("hlog", func() (interface{}, error) {
return hlog.ConfigFromEnv()
})
// Load everything
if err := loader.Load(); err != nil {
log.Fatal(err)
}
// Get configuration
hlogCfg, _ := loader.GetConfig("hlog")
cfg := hlogCfg.(*hlog.Config)
// Use configuration
logger, _ := hlog.NewLogger(cfg, os.Stdout)
logger.Info().Msg("Application started")
}
Configuration
EZConf uses ConfigFromEnv pattern - no environment variables needed for ezconf itself. It reads environment variables as documented in the packages you register.
Built-in Integrations
All golib packages that support configuration include built-in ezconf integration. These packages provide a NewEZConfIntegration() function that returns an Integration object:
- hlog -
hlog.NewEZConfIntegration() - hws -
hws.NewEZConfIntegration() - hwsauth -
hwsauth.NewEZConfIntegration() - tmdb -
tmdb.NewEZConfIntegration()
Using built-in integrations is the recommended approach as it automatically configures the package path and configuration function.
Adding Packages
loader := ezconf.New()
// Add package paths to parse for ENV comments
loader.AddPackagePath("/path/to/golib/hlog")
loader.AddPackagePath("/path/to/golib/hws")
loader.AddPackagePath("/path/to/your/custom/package")
Adding Config Functions
// Register configuration loaders
loader.AddConfigFunc("hlog", func() (interface{}, error) {
return hlog.ConfigFromEnv()
})
loader.AddConfigFunc("hws", func() (interface{}, error) {
return hws.ConfigFromEnv()
})
loader.AddConfigFunc("myapp", func() (interface{}, error) {
return myapp.ConfigFromEnv()
})
Adding Custom Environment Variables
// Add environment variables that aren't in a package config
loader.AddEnvVar(ezconf.EnvVar{
Name: "DATABASE_URL",
Description: "PostgreSQL connection string",
Required: true,
Default: "postgres://localhost/mydb",
})
Detailed Usage
Using Built-in Integrations
The easiest way to use ezconf is with the built-in integrations:
loader := ezconf.New()
// Register a single integration
loader.RegisterIntegration(hlog.NewEZConfIntegration())
// Or register multiple at once
loader.RegisterIntegrations(
hlog.NewEZConfIntegration(),
hws.NewEZConfIntegration(),
hwsauth.NewEZConfIntegration(),
tmdb.NewEZConfIntegration(),
)
loader.Load()
The Integration interface requires three methods:
Name() string- returns the config name (e.g., "hlog")PackagePath() string- returns the path to parse for ENV commentsConfigFunc() func() (interface{}, error)- returns the ConfigFromEnv function
Creating Custom Integrations
You can create integrations for your own packages:
package myapp
import "runtime"
type EZConfIntegration struct{}
func (e EZConfIntegration) Name() string {
return "myapp"
}
func (e EZConfIntegration) PackagePath() string {
_, filename, _, _ := runtime.Caller(0)
return filename[:len(filename)-len("/ezconf.go")]
}
func (e EZConfIntegration) ConfigFunc() func() (interface{}, error) {
return func() (interface{}, error) {
return ConfigFromEnv()
}
}
func NewEZConfIntegration() EZConfIntegration {
return EZConfIntegration{}
}
Then use it:
loader.RegisterIntegration(myapp.NewEZConfIntegration())
Loading Configurations
// Load all registered configurations and parse packages
if err := loader.Load(); err != nil {
log.Fatal(err)
}
// Get a specific configuration
cfg, ok := loader.GetConfig("hlog")
if !ok {
log.Fatal("hlog config not found")
}
// Type assert to the correct type
hlogCfg := cfg.(*hlog.Config)
Getting All Configurations
allConfigs := loader.GetAllConfigs()
for name, cfg := range allConfigs {
fmt.Printf("Config %s: %+v\n", name, cfg)
}
Printing Environment Variables
Printing the environment variables requires loader.ParseEnvVars to have been called.
If passing true to show the values, loader.LoadConfigs must also be called
// Print variable names and descriptions (no values)
if err := loader.PrintEnvVarsStdout(false); err != nil {
log.Fatal(err)
}
// Output:
// LOG_LEVEL # Log level for the logger (default: info)
// LOG_OUTPUT # Output destination for logs (default: console)
// DATABASE_URL # Database connection string (required)
// Print with current values
// Requires loader.LoadConfigs to be called
if err := loader.PrintEnvVarsStdout(true); err != nil {
log.Fatal(err)
}
// Output:
// LOG_LEVEL=debug # Log level for the logger (default: info)
// LOG_OUTPUT=console # Output destination for logs (default: console)
// DATABASE_URL=postgres://localhost/db # Database connection string (required)
Writing to Custom Writer
// Write to a file
file, _ := os.Create("env-vars.txt")
defer file.Close()
if err := loader.PrintEnvVars(file, true); err != nil {
log.Fatal(err)
}
// Write to a buffer
buf := &bytes.Buffer{}
if err := loader.PrintEnvVars(buf, false); err != nil {
log.Fatal(err)
}
Generating .env Files
// Generate new .env file with default values
err := loader.GenerateEnvFile(".env", false)
// Generate with current environment values
err := loader.GenerateEnvFile(".env.production", true)
// Generated file format:
// # Environment Variables Configuration
// # Generated by ezconf
//
// # Log level for the logger (default: info)
// LOG_LEVEL=info
//
// # Database connection string (required)
// DATABASE_URL=postgres://localhost/db
Updating Existing .env Files
// Update existing file, preserving existing values
err := loader.UpdateEnvFile(".env", false)
// Update or create if doesn't exist
err := loader.UpdateEnvFile(".env", true)
When updating:
- Existing variables keep their values
- New variables are added with default values
- Comments are updated to match current documentation
- Variables not in the config are preserved
Getting Environment Variable Metadata
envVars := loader.GetEnvVars()
for _, ev := range envVars {
fmt.Printf("Name: %s\n", ev.Name)
fmt.Printf("Description: %s\n", ev.Description)
fmt.Printf("Required: %t\n", ev.Required)
fmt.Printf("Default: %s\n", ev.Default)
fmt.Printf("Current: %s\n", ev.CurrentValue)
}
ENV Comment Format
Basic Format
type Config struct {
// ENV LOG_LEVEL: Log level for the application
LogLevel string
}
With Default Value
type Config struct {
// ENV LOG_LEVEL: Log level for the application (default: info)
LogLevel string
}
Required Variable
type Config struct {
// ENV DATABASE_URL: Database connection string (required)
DatabaseURL string
}
Conditional Requirement
type Config struct {
// ENV LOG_DIR: Directory for log files (required when LOG_OUTPUT is file) (default: /var/log)
LogDir string
}
Inline Comments
type Config struct {
LogLevel string // ENV LOG_LEVEL: Log level (default: info)
Port int // ENV PORT: Server port (default: 8080)
}
Multiple Modifiers
type Config struct {
// ENV LOG_FILE_NAME: Name of the log file (required when LOG_OUTPUT is file or both) (default: app.log)
LogFileName string
}
Integration
With HLog
loader := ezconf.New()
loader.AddPackagePath("vendor/git.haelnorr.com/h/golib/hlog")
loader.AddConfigFunc("hlog", func() (interface{}, error) {
return hlog.ConfigFromEnv()
})
loader.Load()
// Get hlog config
hlogCfg, _ := loader.GetConfig("hlog")
cfg := hlogCfg.(*hlog.Config)
// Create logger
logger, _ := hlog.NewLogger(cfg, os.Stdout)
With HWS and HWSAuth
loader := ezconf.New()
// Add all packages
loader.AddPackagePath("vendor/git.haelnorr.com/h/golib/hws")
loader.AddPackagePath("vendor/git.haelnorr.com/h/golib/hwsauth")
loader.AddPackagePath("vendor/git.haelnorr.com/h/golib/hlog")
// Add config functions
loader.AddConfigFunc("hws", func() (interface{}, error) {
return hws.ConfigFromEnv()
})
loader.AddConfigFunc("hwsauth", func() (interface{}, error) {
return hwsauth.ConfigFromEnv()
})
loader.AddConfigFunc("hlog", func() (interface{}, error) {
return hlog.ConfigFromEnv()
})
loader.Load()
// Get all configs
hwsCfg, _ := loader.GetConfig("hws")
authCfg, _ := loader.GetConfig("hwsauth")
hlogCfg, _ := loader.GetConfig("hlog")
With Custom Config Structs
package myapp
type Config struct {
// ENV MYAPP_NAME: Application name (default: myapp)
Name string
// ENV MYAPP_DEBUG: Enable debug mode (default: false)
Debug bool
// ENV MYAPP_API_KEY: API key for external service (required)
APIKey string
}
func ConfigFromEnv() (*Config, error) {
cfg := &Config{
Name: env.String("MYAPP_NAME", "myapp"),
Debug: env.Bool("MYAPP_DEBUG", false),
APIKey: env.String("MYAPP_API_KEY", ""),
}
if cfg.APIKey == "" {
return nil, errors.New("MYAPP_API_KEY is required")
}
return cfg, nil
}
// In your main application
loader := ezconf.New()
loader.AddPackagePath("./myapp")
loader.AddConfigFunc("myapp", func() (interface{}, error) {
return myapp.ConfigFromEnv()
})
Best Practices
1. Use Consistent Comment Format
Always document your config struct fields with ENV comments:
type Config struct {
// ENV VARNAME: Description (modifiers)
FieldName string
}
2. Group Related Configurations
Create a single Config struct per package rather than multiple structs:
// Good
type Config struct {
Host string
Port int
TLS bool
}
// Avoid
type HostConfig struct {
Host string
}
type PortConfig struct {
Port int
}
3. Use Descriptive Names
Environment variable names should be clear and prefixed with the package/app name:
// Good
// ENV MYAPP_DATABASE_URL: Database connection string
// Avoid
// ENV DB: Database
4. Document Requirements Clearly
Always indicate if a variable is required and under what conditions:
// ENV LOG_DIR: Directory for log files (required when LOG_OUTPUT is file or both)
5. Provide Sensible Defaults
Include default values for optional configuration:
// ENV LOG_LEVEL: Log level (default: info)
6. Generate .env Templates
Use ezconf to generate .env template files for your application:
// In a separate tool or init command
loader := ezconf.New()
// Add all packages...
loader.Load()
loader.GenerateEnvFile(".env.example", false)
Troubleshooting
ENV Comments Not Being Parsed
Problem: Environment variables are not showing up after parsing.
Solutions:
- Ensure comments start with
ENV(with space after ENV) - Check that the comment includes a colon after the variable name
- Verify the package path is correct and points to the actual source files
- Make sure files are not excluded (like test files)
Config Function Fails to Load
Problem: Load() returns an error from a config function.
Solutions:
- Check that required environment variables are set
- Verify the ConfigFromEnv function handles errors properly
- Ensure the config function signature matches
func() (interface{}, error)
.env File Update Doesn't Work
Problem: UpdateEnvFile doesn't preserve existing values.
Solutions:
- Ensure the file exists or use
createIfNotExist: true - Check file permissions (needs write access)
- Verify the .env file format is correct (KEY=value lines)
Missing Environment Variables in Output
Problem: Some expected variables don't appear in the output.
Solutions:
- Check if the struct field has an ENV comment
- Verify the package was added with
AddPackagePath - Ensure
Load()was called before accessing env vars - Check that the file isn't a test file (*_test.go)
See Also
- HLog - Structured logging with ConfigFromEnv
- HWS - HTTP web server with ConfigFromEnv
- HWSAuth - Authentication with ConfigFromEnv
- Env - Environment variable helpers