Skip to content

Commit

Permalink
feat: config improvements (#69)
Browse files Browse the repository at this point in the history
* docs: imrove code and readme

* fix: fix pointer issue in config
  • Loading branch information
ravisuhag authored Nov 26, 2024
1 parent 7b667a3 commit abb0e1b
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 670 deletions.
166 changes: 105 additions & 61 deletions config/README.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,131 @@
# config
# Config Package

This package is an wrapper over viper with opinionated defaults that allows loading config from a yaml file and environment variables.
The `config` package simplifies configuration management in Go projects by integrating multiple sources (files, environment variables, flags) and decoding them into structured Go objects. It provides defaults, overrides, and extensibility for various use cases.

## Features

- **Flexible Sources**: Load configuration from files (YAML, JSON, etc.), environment variables, and command-line flags.
- **Defaults and Overrides**: Apply default values, with support for environment variable and flag-based overrides.
- **Powerful Decoding**: Decode nested structures, custom types, and JSON strings into Go structs.
- **Customizable**: Configure behavior with options like file paths, environment variable prefixes, and key replacers.

## Installation

Install the package using:

```bash
go get github.com/raystack/salt/config
```

## Usage

### 1. Basic Configuration Loading

Define your configuration struct:

```go
type Config struct {
Host string `yaml:"host" default:"localhost"`
Port int `yaml:"port" default:"8080"`
}
```

Load the configuration:

```go
package main

import (
"encoding/json"
"fmt"

"github.com/raystack/salt/config"
"fmt"
"github.com/raystack/salt/config"
)

type Config struct {
Port int `mapstructure:"port" default:"8080"`
DB DBConfig `mapstructure:"db"`
NewRelic NewRelicConfig `mapstructure:"new_relic"`
LogLevel string `mapstructure:"log_level" default:"info"`
func main() {
var cfg Config
loader := config.NewLoader(
config.WithFile("config.yaml"),
config.WithEnvPrefix("MYAPP"),
)
if err := loader.Load(&cfg); err != nil {
fmt.Println("Error loading configuration:", err)
return
}
fmt.Printf("Configuration: %+v
", cfg)
}
```

type DBConfig struct {
Port int `mapstructure:"port" default:"5432"`
Host string `mapstructure:"host" default:"localhost"`
}
### 2. Using Command-Line Flags

Define your flags and bind them to the configuration struct:

type NewRelicConfig struct {
Enabled bool `mapstructure:"enabled" default:"false"`
AppName string `mapstructure:"app_name" default:"test-app"`
License string `mapstructure:"license"`
```go
import (
"github.com/spf13/pflag"
"github.com/yourusername/config"
)

type Config struct {
Host string `yaml:"host" cmdx:"host"`
Port int `yaml:"port" cmdx:"port"`
}

func main() {
var c Config
l := config.NewLoader(
// config.WithViper(viper.New()), // default
// config.WithName("config"), // default
// config.WithType("yaml"), // default
// config.WithEnvKeyReplacer(".", "_"), // default
config.WithPath("$HOME/.test"),
config.WithEnvPrefix("CONFIG"),
)

if err := l.Load(&c); err != nil { // pass pointer to the struct into which you want to load config
panic(err)
}
s, _ := json.MarshalIndent(c, "", " ") // spaces: 2 | tabs: 1 😛
fmt.Println(string(s))
var cfg Config

pflags := pflag.NewFlagSet("example", pflag.ExitOnError)
pflags.String("host", "localhost", "Server host")
pflags.Int("port", 8080, "Server port")
pflags.Parse([]string{"--host", "127.0.0.1", "--port", "9090"})

loader := config.NewLoader(
config.WithFile("config.yaml"),
config.WithBindPFlags(pflags, &cfg),
)
if err := loader.Load(&cfg); err != nil {
fmt.Println("Error loading configuration:", err)
return
}
fmt.Printf("Configuration: %+v
", cfg)
}
```

In the above program a YAML file or environment variables can be used to configure.

```yaml
port: 9000
db:
port: 5432
host: db-host-yaml
new_relic:
enabled: true
app_name: config-test-yaml
license: ____LICENSE_STRING_OF_40_CHARACTERS_____
log_level: debug
```
### 3. Environment Variable Overrides

or
Override configuration values using environment variables:

```sh
export CONFIG_PORT=9001
export CONFIG_DB_PORT=5432
export CONFIG_DB_HOST=db-host-env
export CONFIG_NEW_RELIC_ENABLED=true
export CONFIG_NEW_RELIC_APP_NAME=config-test-env
export CONFIG_NEW_RELIC_LICENSE=____LICENSE_STRING_OF_40_CHARACTERS_____
export CONFIG_LOG_LEVEL=debug
```go
loader := config.NewLoader(
config.WithEnvPrefix("MYAPP"),
)
```

or a mix of both.
Set environment variables like `MYAPP_HOST` or `MYAPP_PORT` to override `host` and `port` values.

## Advanced Features

- **Custom Decode Hooks**: Parse custom formats like JSON strings into maps.
- **Error Handling**: Handles missing files gracefully and provides detailed error messages.
- **Multiple Config Paths**: Search for configuration files in multiple directories using `WithPath`.

## API Reference

### Loader Options

- `WithFile(file string)`: Set the explicit file path for the configuration file.
- `WithPath(path string)`: Add directories to search for configuration files.
- `WithName(name string)`: Set the name of the configuration file (without extension).
- `WithType(type string)`: Set the file type (e.g., "json", "yaml").
- `WithEnvPrefix(prefix string)`: Set a prefix for environment variables.
- `WithBindPFlags(flagSet *pflag.FlagSet, config interface{})`: Bind CLI flags to configuration fields.

### Custom Hooks

**Configs set in environment will override the ones set as default and in yaml file.**
- `StringToJsonFunc()`: Decode JSON strings into maps or other structures.

## TODO
### Struct Tags

- function to print/return config keys in yaml path and env format with defaults as helper
- add support for flags
- `yaml`: Maps struct fields to YAML keys.
- `default`: Specifies default values for struct fields.
- `cmdx`: Binds struct fields to command-line flags.
Loading

0 comments on commit abb0e1b

Please sign in to comment.