Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
go-version: '1.21'

- name: Build
run: go build -v ./...
Expand Down
13 changes: 13 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

version: 2


formatters:
enable:
- gofmt
- gofumpt
- goimports
linters:
enable:
- staticcheck
- errcheck
32 changes: 31 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Changelog

## v0.7

### Breaking Changes

Field names now can be renamed at struct level instead of globally by prefixing them with a `.`.

So that in the following example, the config Redis.Host will be mapped to Redis.Address
While Port will be mapped to REDIS_SERVICE_PORT.


```go
type Redis struct {
Host string `unconfig:".Address"`
Port string `unconfig:"REDIS_SERVICE_PORT"`
}


type Config struct {
Redis Redis
}
```




## v0.6

Artificial release to retract v1.


## v0.5

### Breaking Changes
Expand Down Expand Up @@ -27,7 +57,7 @@ type Files []struct {
}
```

to
to

```go
type Files []struct {
Expand Down
144 changes: 63 additions & 81 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Lightweight, zero-dependency, and extendable configuration management.

uConfig is extremely light and extendable configuration management library with zero dependencies. Every aspect of configuration is provided through a _plugin_, which means you can have any combination of flags, environment variables, defaults, secret providers, Kubernetes Downward API, and what you want, and only what you want, through plugins.
uConfig is extremely light and extendable configuration management library with zero dependencies. Every aspect of configuration is provided through a _plugin_, which means you can have any combination of flags, environment variables, defaults, secret providers, Kubernetes Downward API, and any combination of configuration files and formats including json, toml, cue, or just about anything you want, and only what you want, through plugins.


uConfig takes the config schema as a struct decorated with tags, nesting is supported.
Expand Down Expand Up @@ -37,48 +37,37 @@ type Config struct {
```go
package main



import (
"encoding/json"
"encoding/json"
"fmt"
"os"

"github.com/omeid/uconfig"
"github.com/omeid/uconfig"

"$PROJECT/redis"
"$PROJECT/database"
"github.com/omeid/uconfig/examples/sample/database"
"github.com/omeid/uconfig/examples/sample/redis"
)

// Config is our application config.
type Config struct {
// yes you can have slices.
Hosts []string `default:"localhost,localhost.local" usage:"the ip or domains to bind to"`

// yes you can have slices.
Hosts []string `default:"localhost,localhost.local" usage:"the ip or domains to bind to"`

Redis redis.Config
Database database.Config

Redis redis.Config
Database database.Config
}

var files = uconfig.Files{
{"config.json", json.Unmarshal, true},
// you can of course add as many files
// as you want, and they will be applied
// in the given order.
}

var conf = uconfig.Classic[Config](files)

func main() {

conf := &Config{}

files := uconfig.Files{
{"config.json", json.Unmarshal, true},
// you can of course add as many files
// as you want, and they will be applied
// in the given order.
}

_, err := uconfig.Classic(&conf, files)

if err != nil {
fmt.Println(err)
os.Exit(1)
}

conf := conf.Run()
// use conf as you please.
// let's pretty print it as JSON for example:
configAsJson, err := json.MarshalIndent(conf, "", " ")
Expand All @@ -88,7 +77,6 @@ func main() {
}

fmt.Print(string(configAsJson))

}
```

Expand Down Expand Up @@ -140,25 +128,27 @@ Sometimes you might want to use a different env var, or flag name for backwards

1. uconfig tag

You can change the name of a field as seen by unconfig.
This option supports the usual nesting prefixing.
See the port example below.
You can change the name of a field as seen by `uconfig`.

Please not that this flag only works for walker plugins (flags, env, anything flat) and for Visitor plugins (file, stream, et al) you will need
to use encoder specific tags like `json:"field_name"` and so on.


2. Plugin specific tags

Most plugins support controlling the field name as seen by that specific plugin.
Most plugins support controlling the field name as seen by that specific plugin. For example `env:"DB_NAME"`.

This option does not support nesting prefixes.
See the Database field in the example below.

For both type of tags, you can prefix them with `.` to rename the field only at the struct level.
See the `Service.Port` and DB_NAME examples below.

```go
package database

// Config holds the database configurations.
type Database struct {
Address string `default:"localhost"`
Port string `default:"28015" uconfig:"Service.Port"`
Port string `default:"28015" uconfig:".Service.Port"`
Database string `default:"my-project" env:"DB_NAME" flag:"main-db-name"`
}
```
Expand Down Expand Up @@ -216,62 +206,56 @@ The secret provider allows you to grab the value of a config from anywhere you w
Unlike most other plugins, secret requires explicit `secret:""` tag, this is because only specific config values like passwords and api keys come from a secret provider, compared to the rest of the config which can be set in various ways.

```go
package main

import (
"encoding/json"
"fmt"

"github.com/omeid/uconfig"
"github.com/omeid/uconfig/plugins/secret"
"github.com/omeid/uconfig"
"github.com/omeid/uconfig/examples/secrets/secretsource"
"github.com/omeid/uconfig/plugins/secret"
)

// Creds is an example of a config struct that uses secret values.
type Creds struct {
// by default, secret plugin will generate a name that is identical
// to env plugin, SCREAM_SNAKE_CASE, so in this case it will be
// APIKEY however, following the standard uConfig nesting rules
// in Config struct below, it becomes CREDS_APIKEY.
APIKey string `secret:""`
// or you can provide your own name, which will not be impacted
// by nesting or the field name.
APIToken string `secret:"API_TOKEN"`
// by default, secret plugin will generate a name that is identical
// to env plugin, SCREAM_SNAKE_CASE, so in this case it will be
// APIKEY however, following the standard uConfig nesting rules
// in Config struct below, it becomes CREDS_APIKEY.
APIKey string `secret:""`
// or you can provide your own name, which will not be impacted
// by nesting or the field name.
APIToken string `secret:"API_TOKEN"`
}

type Config struct {
Redis Redis
Creds Creds
Creds Creds
}

var files = uconfig.Files{
{"config.json", json.Unmarshal, false},
}

func main() {

conf := &Config{}


files := uconfig.Files{
{"config.json", json.Unmarshal, false}
}

// secret.New accepts a function that maps a secret name to it's value.
secretPlugin := secret.New(func(name string) (string, error) {
// you're free to grab the secret based on the name from wherever
// you please, aws secrets-manager, hashicorp vault, or wherever.
value, ok := secretSource.Get(name)

if !ok {
return "", ErrSecretNotFound
}
var secrets = secret.New(func(name string) (string, error) {
// you're free to grab the secret based on the name from wherever
// you please, aws secrets-manager, hashicorp vault, or wherever.
value, ok := secretsource.Get(name)
if !ok {
return "", secret.ErrSecretNotFound
}

return value, nil
})
return value, nil
})

// then you can use the secretPlugin with uConfig like any other plugin.
// Lucky, uconfig.Classic allows passing more plugins, which means
// you can simply do the following for flags, envs, files, and secrets!
_, err := uconfig.Classic(&conf, files, secretPlugin)
if err != nil {
t.Fatal(err)
}
func main() {
// then you can use the secretPlugin with uConfig like any other plugin.
// Lucky, uconfig.Classic allows passing more plugins, which means
// you can simply do the following for flags, envs, files, and secrets!
conf := uconfig.Classic[Config](files, secrets).Run()

fmt.Printf("we got an API Key: %s\n", conf.Creds.APIKey)
}

```


Expand All @@ -290,10 +274,8 @@ import (

func TestSomething(t *testing.T) error {

conf := &YourConfigStruct{}

// It will panic on error
uconfig.Must(conf, defaults.New())
conf := uconfig.Must[Conf](defaults.New())

// Use your conf as you please.
}
Expand Down
32 changes: 9 additions & 23 deletions classic.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package uconfig

import (
"errors"
"os"

"github.com/omeid/uconfig/plugins"
"github.com/omeid/uconfig/plugins/defaults"
"github.com/omeid/uconfig/plugins/env"
Expand All @@ -17,32 +14,21 @@ type Files = file.Files
// Classic creates a uconfig manager with defaults,environment variables,
// and flags (in that order) and optionally file loaders based on the provided
// Files map and parses them right away.
func Classic(conf interface{}, files Files, userPlugins ...plugins.Plugin) (Config, error) {

fps := files.Plugins()

ps := make([]plugins.Plugin, 0, len(fps)+3+len(userPlugins))

func Classic[C any](files Files, userPlugins ...plugins.Plugin) Config[C] {
// almost a duplicate of Load, but due to the order of things, not worth abstracting for a few lines.
ps := make([]plugins.Plugin, 0, len(files)+3+len(userPlugins))
// first defaults
ps = append(ps, defaults.New())
// then files
ps = append(ps, fps...)
// followed by env and flags
ps = append(ps, env.New(), flag.Standard())
ps = append(ps, files.Plugins()...)
// then any user pugins, often just _secret_.
ps = append(ps, userPlugins...)

c, err := New(conf, ps...)

if err != nil {
return c, err
}
// followed by envs
ps = append(ps, env.New())

err = c.Parse()
if errors.Is(err, ErrUsage) {
c.Usage()
os.Exit(0)
}
// and lastly flags.
ps = append(ps, flag.Standard())

return c, err
return New[C](ps...)
}
Loading