Documentation
¶
Overview ¶
Package goenvsubst provides functionality to recursively replace environment variable references in Go data structures with their actual values from the environment.
The package supports various Go data types including structs, slices, maps, arrays, and pointers, both as top-level inputs and nested within other structures.
Environment variables should be referenced in the format $VAR_NAME. If an environment variable is not set or is empty, it will be replaced with an empty string.
Basic Usage ¶
The main function Do() accepts any Go data structure and modifies it in-place:
import "github.com/iamolegga/goenvsubst"
// Set environment variable
os.Setenv("DATABASE_URL", "postgres://localhost:5432/mydb")
config := &struct {
DatabaseURL string
Debug bool
}{
DatabaseURL: "$DATABASE_URL",
Debug: true,
}
err := goenvsubst.Do(config)
if err != nil {
log.Fatal(err)
}
// config.DatabaseURL is now "postgres://localhost:5432/mydb"
Struct Fields ¶
Environment variable substitution works with any string field in a struct:
type Config struct {
Host string
Port string
Username string
Password string
}
config := &Config{
Host: "$DB_HOST",
Port: "$DB_PORT",
Username: "$DB_USER",
Password: "$DB_PASS",
}
goenvsubst.Do(config)
Slices and Arrays ¶
String elements in slices and arrays are processed:
// Slice example
urls := []string{"$API_URL", "$BACKUP_URL", "https://static.example.com"}
goenvsubst.Do(&urls)
// Array example
servers := [3]string{"$SERVER1", "$SERVER2", "$SERVER3"}
goenvsubst.Do(&servers)
Maps ¶
Map values (but not keys) are processed for environment variable substitution:
config := map[string]string{
"database_url": "$DATABASE_URL",
"redis_url": "$REDIS_URL",
"api_key": "$API_KEY",
}
goenvsubst.Do(&config)
Nested Structures ¶
The package handles deeply nested structures:
type DatabaseConfig struct {
URL string
Username string
Password string
}
type AppConfig struct {
Database DatabaseConfig
Services []string
Env map[string]string
}
config := &AppConfig{
Database: DatabaseConfig{
URL: "$DATABASE_URL",
Username: "$DB_USER",
Password: "$DB_PASS",
},
Services: []string{"$SERVICE1", "$SERVICE2"},
Env: map[string]string{
"LOG_LEVEL": "$LOG_LEVEL",
"DEBUG": "$DEBUG_MODE",
},
}
goenvsubst.Do(config)
Pointers ¶
The package safely handles pointers, including nil pointers:
var config *struct {
Value string
}
// Safe to call with nil pointer
goenvsubst.Do(&config) // No error, no operation
// With actual pointer
config = &struct{ Value string }{"$MY_VALUE"}
goenvsubst.Do(config)
Complex Example ¶
A real-world configuration structure:
type ServerConfig struct {
Host string `json:"host"`
Port string `json:"port"`
}
type AppConfig struct {
Server ServerConfig `json:"server"`
Database string `json:"database"`
Redis string `json:"redis"`
Services []string `json:"services"`
Features map[string]bool `json:"features"`
Endpoints map[string]string `json:"endpoints"`
Secrets map[string]*string `json:"secrets"`
}
// Set environment variables
os.Setenv("APP_HOST", "0.0.0.0")
os.Setenv("APP_PORT", "8080")
os.Setenv("DATABASE_URL", "postgres://localhost/myapp")
os.Setenv("REDIS_URL", "redis://localhost:6379")
os.Setenv("API_ENDPOINT", "https://api.example.com")
config := &AppConfig{
Server: ServerConfig{
Host: "$APP_HOST",
Port: "$APP_PORT",
},
Database: "$DATABASE_URL",
Redis: "$REDIS_URL",
Services: []string{"$SERVICE_AUTH", "$SERVICE_PAYMENT"},
Features: map[string]bool{
"feature_a": true,
"feature_b": false,
},
Endpoints: map[string]string{
"api": "$API_ENDPOINT",
"webhook": "$WEBHOOK_URL",
},
Secrets: map[string]*string{
"jwt_secret": func() *string { s := "$JWT_SECRET"; return &s }(),
},
}
err := goenvsubst.Do(config)
if err != nil {
log.Fatal(err)
}
Error Handling ¶
The Do function returns an error if there are issues during processing. Currently, the function is designed to be robust and typically returns nil, but error handling is provided for future extensibility:
err := goenvsubst.Do(config)
if err != nil {
log.Printf("Failed to substitute environment variables: %v", err)
return
}
Important Notes ¶
- Only string values are processed for environment variable substitution - Map keys are never modified, only values - Missing or empty environment variables are replaced with empty strings - The function modifies the input data structure in-place - Nil pointers are handled safely without causing panics - The function is safe for concurrent use as it doesn't modify global state
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Do ¶
Do recursively walks through any Go data structure (structs, slices, maps, arrays, pointers) and replaces environment variable references in string values with their actual values from the environment. Environment variables should be in the format $VAR_NAME. Supports top-level and nested: structs, slices, arrays, maps, and pointers.
Example ¶
ExampleDo demonstrates basic usage with a struct
package main
import (
"fmt"
"os"
"github.com/iamolegga/goenvsubst"
)
func main() {
// Set up environment variables
os.Setenv("DATABASE_URL", "postgres://localhost:5432/mydb")
os.Setenv("API_KEY", "secret123")
defer func() {
os.Unsetenv("DATABASE_URL")
os.Unsetenv("API_KEY")
}()
config := &struct {
DatabaseURL string
APIKey string
Debug bool
}{
DatabaseURL: "$DATABASE_URL",
APIKey: "$API_KEY",
Debug: true,
}
err := goenvsubst.Do(config)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Database URL: %s\n", config.DatabaseURL)
fmt.Printf("API Key: %s\n", config.APIKey)
fmt.Printf("Debug: %t\n", config.Debug)
}
Output: Database URL: postgres://localhost:5432/mydb API Key: secret123 Debug: true
Example (Array) ¶
ExampleDo_array demonstrates usage with arrays
package main
import (
"fmt"
"os"
"github.com/iamolegga/goenvsubst"
)
func main() {
// Set up environment variables
os.Setenv("SERVER1", "server1.example.com")
os.Setenv("SERVER2", "server2.example.com")
defer func() {
os.Unsetenv("SERVER1")
os.Unsetenv("SERVER2")
}()
servers := [3]string{"$SERVER1", "$SERVER2", "server3.example.com"}
err := goenvsubst.Do(&servers)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
for i, server := range servers {
fmt.Printf("Server %d: %s\n", i+1, server)
}
}
Output: Server 1: server1.example.com Server 2: server2.example.com Server 3: server3.example.com
Example (Map) ¶
ExampleDo_map demonstrates usage with maps
package main
import (
"fmt"
"os"
"github.com/iamolegga/goenvsubst"
)
func main() {
// Set up environment variables
os.Setenv("REDIS_URL", "redis://localhost:6379")
os.Setenv("MONGO_URL", "mongodb://localhost:27017")
defer func() {
os.Unsetenv("REDIS_URL")
os.Unsetenv("MONGO_URL")
}()
config := map[string]string{
"redis": "$REDIS_URL",
"mongodb": "$MONGO_URL",
"static": "https://cdn.example.com",
"$ENV_KEY": "this-key-wont-change", // Keys are not substituted
}
err := goenvsubst.Do(&config)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Redis: %s\n", config["redis"])
fmt.Printf("MongoDB: %s\n", config["mongodb"])
fmt.Printf("Static: %s\n", config["static"])
}
Output: Redis: redis://localhost:6379 MongoDB: mongodb://localhost:27017 Static: https://cdn.example.com
Example (NestedStructure) ¶
ExampleDo_nestedStructure demonstrates usage with complex nested structures
package main
import (
"fmt"
"os"
"github.com/iamolegga/goenvsubst"
)
func main() {
// Set up environment variables
os.Setenv("DB_HOST", "localhost")
os.Setenv("DB_PORT", "5432")
os.Setenv("API_ENDPOINT", "https://api.example.com")
defer func() {
os.Unsetenv("DB_HOST")
os.Unsetenv("DB_PORT")
os.Unsetenv("API_ENDPOINT")
}()
type DatabaseConfig struct {
Host string
Port string
}
type AppConfig struct {
Database DatabaseConfig
Services []string
Endpoints map[string]string
}
config := &AppConfig{
Database: DatabaseConfig{
Host: "$DB_HOST",
Port: "$DB_PORT",
},
Services: []string{"$SERVICE1", "static-service"},
Endpoints: map[string]string{
"api": "$API_ENDPOINT",
"health": "/health",
},
}
err := goenvsubst.Do(config)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Database Host: %s\n", config.Database.Host)
fmt.Printf("Database Port: %s\n", config.Database.Port)
fmt.Printf("API Endpoint: %s\n", config.Endpoints["api"])
fmt.Printf("Health Endpoint: %s\n", config.Endpoints["health"])
if config.Services[0] == "" {
fmt.Printf("Service 1:\n")
} else {
fmt.Printf("Service 1: %s\n", config.Services[0])
}
fmt.Printf("Service 2: %s\n", config.Services[1])
}
Output: Database Host: localhost Database Port: 5432 API Endpoint: https://api.example.com Health Endpoint: /health Service 1: Service 2: static-service
Example (Pointers) ¶
ExampleDo_pointers demonstrates usage with pointers
package main
import (
"fmt"
"os"
"github.com/iamolegga/goenvsubst"
)
func main() {
// Set up environment variables
os.Setenv("SECRET_KEY", "my-secret-key")
defer os.Unsetenv("SECRET_KEY")
// Pointer to string
secretValue := "$SECRET_KEY"
err := goenvsubst.Do(&secretValue)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Secret: %s\n", secretValue)
// Nil pointer (safe to process)
var nilPtr *string
err = goenvsubst.Do(&nilPtr)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Nil pointer handled safely: %v\n", nilPtr == nil)
}
Output: Secret: my-secret-key Nil pointer handled safely: true
Example (Slice) ¶
ExampleDo_slice demonstrates usage with slices
package main
import (
"fmt"
"os"
"github.com/iamolegga/goenvsubst"
)
func main() {
// Set up environment variables
os.Setenv("SERVICE1", "auth-service")
os.Setenv("SERVICE2", "payment-service")
defer func() {
os.Unsetenv("SERVICE1")
os.Unsetenv("SERVICE2")
}()
services := []string{"$SERVICE1", "$SERVICE2", "static-service"}
err := goenvsubst.Do(&services)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
for i, service := range services {
fmt.Printf("Service %d: %s\n", i+1, service)
}
}
Output: Service 1: auth-service Service 2: payment-service Service 3: static-service
Types ¶
This section is empty.