Skip to content

Commit

Permalink
improved profiles ++ added example ++ updated readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Hasan Ozgan committed May 9, 2021
1 parent 99ded1c commit 8e65b9b
Show file tree
Hide file tree
Showing 14 changed files with 164 additions and 32 deletions.
16 changes: 15 additions & 1 deletion README.md
Expand Up @@ -37,7 +37,7 @@ build: "2020-01-09T12:30:00Z"

server:
ports:
- 8080
- "${SERVER_PORT:8080}"
cleanup: 1h

logger:
Expand Down Expand Up @@ -91,6 +91,20 @@ fig.Load(&cfg,

```

### Profiles

You can use `profiles` for other environments

```go
fig.Load(&cfg,
fig.File("settings.json"),
fig.Dirs(".", "/etc/myapp", "/home/user/myapp"),
fig.Profiles("test", "integration") // searches settings-test.json, settings-integration.json in each folder
fig.ProfileLayout("config-test.yaml") // DEFAULT: config.test.yaml
) // searches for ./settings.json, /etc/myapp/settings.json, /home/user/myapp/settings.json

```

## Environment

Need to additionally fill fields from the environment? It's as simple as:
Expand Down
9 changes: 9 additions & 0 deletions examples/profile/config-integration.yaml
@@ -0,0 +1,9 @@
database:
host: "db"
port: 5432
name: "users"
username: "admin"
password: "postgres"
kafka:
host:
- "kafka"
9 changes: 9 additions & 0 deletions examples/profile/config-test.yaml
@@ -0,0 +1,9 @@
database:
host: "sqlite:file.db"
port: -1
name: "users"
username: ""
password: ""
kafka:
host:
- "embedded:kafka"
10 changes: 10 additions & 0 deletions examples/profile/config.yaml
@@ -0,0 +1,10 @@
database:
host: "db.prod.example.com"
port: 5432
name: "${DATABASE_NAME:users}"
username: "admin"
password: "S3cr3t-P455w0rd"
kafka:
host:
- "kafka1.prod.example.com"
- "kafka2.prod.example.com"
63 changes: 63 additions & 0 deletions examples/profile/profile_test.go
@@ -0,0 +1,63 @@
package profile

import (
"fmt"
"os"

"github.com/kkyr/fig"
)

type Config struct {
Database struct {
Host string `fig:"host" validate:"required"`
Port int `fig:"port"`
Name string `fig:"name" validate:"required"`
Username string `fig:"username"`
Password string `fig:"password"`
}
Kafka struct {
Host []string `fig:"host" validate:"required"`
}
}

func ExampleLoad() {
var cfg Config
if err := fig.Load(&cfg); err == nil {
fmt.Printf("%+v", cfg)
}

// Output:
// {Database:{Host:db.prod.example.com Port:5432 Name:users Username:admin Password:S3cr3t-P455w0rd} Kafka:{Host:[kafka1.prod.example.com kafka2.prod.example.com]}}
}

func ExampleLoad_with_environment_in_config_file() {
os.Setenv("DATABASE_NAME", "users-readonly")

var cfg Config
if err := fig.Load(&cfg); err == nil {
fmt.Printf("%+v", cfg)
}

// Output:
// {Database:{Host:db.prod.example.com Port:5432 Name:users-readonly Username:admin Password:S3cr3t-P455w0rd} Kafka:{Host:[kafka1.prod.example.com kafka2.prod.example.com]}}
}

func ExampleLoad_with_multi_profile() {
var cfg Config
if err := fig.Load(&cfg, fig.Profiles("test", "integration"), fig.ProfileLayout("config-test.yaml")); err == nil {
fmt.Printf("%+v", cfg)
}

// Output:
// {Database:{Host:db Port:5432 Name:users Username:admin Password:postgres} Kafka:{Host:[kafka]}}
}

func ExampleLoad_with_single_profile() {
var cfg Config
if err := fig.Load(&cfg, fig.Profiles("test"), fig.ProfileLayout("config-test.yaml")); err == nil {
fmt.Printf("%+v", cfg)
}

// Output:
// {Database:{Host:sqlite:file.db Port:-1 Name:users Username: Password:} Kafka:{Host:[embedded:kafka]}}
}
17 changes: 8 additions & 9 deletions fig.go
Expand Up @@ -83,8 +83,7 @@ type fig struct {
timeLayout string
useEnv bool
envPrefix string
useProfile bool
profile string
profiles []string
profileLayout string
}

Expand All @@ -103,8 +102,8 @@ func (f *fig) Load(cfg interface{}) error {
return err
}

if f.useProfile {
profileFile, err := f.findProfileCfgFile()
for _, profile := range f.profiles {
profileFile, err := f.findProfileCfgFile(profile)
if err != nil {
return err
}
Expand All @@ -114,7 +113,7 @@ func (f *fig) Load(cfg interface{}) error {
return fmt.Errorf("%v, filename: %s", err, profileFile)
}

if err := mergo.MergeWithOverwrite(&vals, profileVals, mergo.WithSliceDeepCopy, mergo.WithTypeCheck); err != nil {
if err := mergo.Merge(&vals, profileVals, mergo.WithOverride, mergo.WithTypeCheck); err != nil {
return err
}
}
Expand All @@ -126,17 +125,17 @@ func (f *fig) Load(cfg interface{}) error {
return f.processCfg(cfg)
}

func (f *fig) profileFileName() string {
func (f *fig) profileFileName(profile string) string {
filename := f.profileLayout
parts := strings.Split(f.filename, ".")
filename = strings.ReplaceAll(filename, "config", parts[0])
filename = strings.ReplaceAll(filename, "test", f.profile)
filename = strings.ReplaceAll(filename, "test", profile)
filename = strings.ReplaceAll(filename, "yaml", parts[1])
return filename
}

func (f *fig) findProfileCfgFile() (path string, err error) {
file := f.profileFileName()
func (f *fig) findProfileCfgFile(profile string) (path string, err error) {
file := f.profileFileName(profile)
for _, dir := range f.dirs {
path = filepath.Join(dir, file)
if fileExists(path) {
Expand Down
21 changes: 12 additions & 9 deletions fig_test.go
Expand Up @@ -482,26 +482,31 @@ func Test_fig_Load_Server_If_Env_Set_In_Conf_File(t *testing.T) {
func Test_fig_Load_Server_With_Profile(t *testing.T) {
for _, f := range []string{"server.yaml", "server.json", "server.toml"} {
t.Run(f, func(t *testing.T) {
fmt.Println(f)
type Server struct {
Host string `fig:"host"`
Logger struct {
LogLevel string `fig:"log_level" default:"info"`
Appender string `fig:"appender"`
}
Replicas []string
}

var cfg Server
err := Load(&cfg,
File(f),
Dirs(filepath.Join("testdata", "valid")),
UseProfile("test"),
UseProfileLayout("config.test.yaml"),
Profiles("test"),
ProfileLayout("config.test.yaml"),
)
if err != nil {
t.Fatalf("expected err %v", err)
}

want := Server{Host: "192.168.0.256"}
want.Logger.LogLevel = "debug"
want.Logger.LogLevel = "error"
want.Logger.Appender = "file"
want.Replicas = []string{"xyz"}

if !reflect.DeepEqual(want, cfg) {
t.Errorf("\nwant %+v\ngot %+v", want, cfg)
Expand Down Expand Up @@ -530,8 +535,8 @@ func Test_fig_Load_Server_With_Profile_When_Config_Is_Invalid(t *testing.T) {
filepath.Join("testdata", "valid"),
filepath.Join("testdata", "invalid"),
),
UseProfile(test.profile),
UseProfileLayout("config-test.yaml"),
Profiles(test.profile),
ProfileLayout("config-test.yaml"),
)

if err == nil {
Expand Down Expand Up @@ -577,10 +582,9 @@ func Test_fig_findProfileCfgFile(t *testing.T) {
t.Run("finds existing file", func(t *testing.T) {
fig := defaultFig()
fig.filename = "server.yaml"
fig.profile = "test"
fig.dirs = []string{".", "testdata", filepath.Join("testdata", "valid")}

file, err := fig.findProfileCfgFile()
file, err := fig.findProfileCfgFile("test")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
Expand All @@ -594,10 +598,9 @@ func Test_fig_findProfileCfgFile(t *testing.T) {
t.Run("non-existing file returns ErrFileNotFound", func(t *testing.T) {
fig := defaultFig()
fig.filename = "server.yaml"
fig.profile = "e2e"
fig.dirs = []string{".", "testdata", filepath.Join("testdata", "valid")}

file, err := fig.findProfileCfgFile()
file, err := fig.findProfileCfgFile("e2e")
if err == nil {
t.Fatalf("expected err, got file %s", file)
}
Expand Down
11 changes: 5 additions & 6 deletions option.go
Expand Up @@ -95,24 +95,23 @@ func UseEnv(prefix string) Option {
}
}

// UseProfile returns an option that configures the profile key that fig uses
// Profiles returns an option that configures the profile key that fig uses
//
// fig.Load(&cfg, fig.UseProfile("test"))
//
// If this option is not used then fig uses the tag `fig`.
func UseProfile(profile string) Option {
func Profiles(profiles ...string) Option {
return func(f *fig) {
f.useProfile = true
f.profile = profile
f.profiles = profiles
}
}

// UseProfileLayout returns an option that configures the profile layout that fig uses
// ProfileLayout returns an option that configures the profile layout that fig uses
//
// fig.Load(&cfg, fig.UseProfileLayout("config-test.yaml"))
//
// If this option is not used then fig uses the tag `fig`.
func UseProfileLayout(layout string) Option {
func ProfileLayout(layout string) Option {
return func(f *fig) {
f.profileLayout = layout
}
Expand Down
6 changes: 4 additions & 2 deletions testdata/valid/server.json
@@ -1,6 +1,8 @@
{
"host": "${SERVICE_HOST:0.0.0.0}",
"logger": {
"log_level": "debug"
}
"log_level": "debug",
"appender": "file"
},
"replicas": [ "abc","xyz" ]
}
6 changes: 5 additions & 1 deletion testdata/valid/server.test.json
@@ -1,3 +1,7 @@
{
"host": "192.168.0.256"
"host": "192.168.0.256",
"logger": {
"log_level": "error"
},
"replicas": [ "xyz" ]
}
9 changes: 8 additions & 1 deletion testdata/valid/server.test.toml
@@ -1 +1,8 @@
host = "192.168.0.256"
host = "192.168.0.256"
replicas = [
"xyz"
]

[logger]
log_level = "error"

6 changes: 5 additions & 1 deletion testdata/valid/server.test.yaml
@@ -1 +1,5 @@
host: "192.168.0.256"
host: "192.168.0.256"
logger:
log_level: "error"
replicas:
- "xyz"
6 changes: 5 additions & 1 deletion testdata/valid/server.toml
@@ -1,4 +1,8 @@
host = "${SERVICE_HOST:0.0.0.0}"
replicas = [
"abc", "xyz"
]

[logger]
log_level = "debug"
log_level = "debug"
appender = "file"
7 changes: 6 additions & 1 deletion testdata/valid/server.yaml
@@ -1,4 +1,9 @@
host: "${SERVICE_HOST:0.0.0.0}"

logger:
log_level: "debug"
log_level: "debug"
appender: "file"

replicas:
- abc
- xyz

0 comments on commit 8e65b9b

Please sign in to comment.