Skip to content

Commit

Permalink
Issue #79: Refactor config loading to use flag sets
Browse files Browse the repository at this point in the history
* support cmdline, env vars and properties
* support env vars with and without a FABIO_ prefix
* support loading properties from URL
* support consul agent on https

Inspired by PR #63 from @doublerebel
  • Loading branch information
magiconair committed May 17, 2016
1 parent 2a5be3a commit 86e74c7
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 262 deletions.
14 changes: 9 additions & 5 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type Config struct {
Proxy Proxy
Registry Registry
Listen []Listen
Metrics []Metrics
Metrics Metrics
UI UI
Runtime Runtime
}
Expand Down Expand Up @@ -35,10 +35,13 @@ type Proxy struct {
DialTimeout time.Duration
ResponseHeaderTimeout time.Duration
KeepAliveTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
LocalIP string
ClientIPHeader string
TLSHeader string
TLSHeaderValue string
ListenerAddr string
}

type Runtime struct {
Expand All @@ -47,10 +50,10 @@ type Runtime struct {
}

type Metrics struct {
Target string
Prefix string
Interval time.Duration
Addr string
Target string
Prefix string
Interval time.Duration
GraphiteAddr string
}

type Registry struct {
Expand All @@ -70,6 +73,7 @@ type File struct {

type Consul struct {
Addr string
Scheme string
Token string
KVPath string
TagPrefix string
Expand Down
27 changes: 10 additions & 17 deletions config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ import (

var Default = &Config{
Proxy: Proxy{
MaxConn: 10000,
Strategy: "rnd",
Matcher: "prefix",
DialTimeout: 30 * time.Second,
LocalIP: LocalIPString(),
MaxConn: 10000,
Strategy: "rnd",
Matcher: "prefix",
DialTimeout: 30 * time.Second,
LocalIP: LocalIPString(),
ListenerAddr: ":9999",
},
Registry: Registry{
Backend: "consul",
Consul: Consul{
Addr: "localhost:8500",
Scheme: "http",
KVPath: "/fabio/config",
TagPrefix: "urlprefix-",
ServiceAddr: ":9998",
Expand All @@ -25,11 +27,6 @@ var Default = &Config{
CheckTimeout: 3 * time.Second,
},
},
Listen: []Listen{
{
Addr: ":9999",
},
},
Runtime: Runtime{
GOGC: 800,
GOMAXPROCS: runtime.NumCPU(),
Expand All @@ -38,12 +35,8 @@ var Default = &Config{
Addr: ":9998",
Color: "light-green",
},
Metrics: []Metrics{
{
Target: "",
Prefix: "default",
Addr: "",
Interval: 30 * time.Second,
},
Metrics: Metrics{
Prefix: "default",
Interval: 30 * time.Second,
},
}
79 changes: 79 additions & 0 deletions config/flagset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package config

import (
"flag"
"strings"

"github.com/magiconair/properties"
)

type FlagSet struct {
flag.FlagSet
set map[string]bool
}

func NewFlagSet(name string, errorHandling flag.ErrorHandling) *FlagSet {
fs := &FlagSet{set: make(map[string]bool)}
fs.Init(name, errorHandling)
return fs
}

// IsSet returns true if a variable was set via any mechanism.
func (f *FlagSet) IsSet(name string) bool {
return f.set[name]
}

// ParseFlags parses command line arguments and provides fallback
// values from environment variables and config file values.
// Environment variables are case-insensitive and can have either
// of the provided prefixes.
func (f *FlagSet) ParseFlags(args, environ, prefixes []string, p *properties.Properties) error {
if err := f.Parse(args); err != nil {
return err
}

if len(prefixes) == 0 {
prefixes = []string{""}
}

// parse environment in case-insensitive way
env := map[string]string{}
for _, e := range environ {
p := strings.Split(e, "=")
env[strings.ToUpper(p[0])] = p[1]
}

// determine all values that were set via cmdline
f.Visit(func(fl *flag.Flag) {
f.set[fl.Name] = true
})

// lookup the rest via environ and properties
f.VisitAll(func(fl *flag.Flag) {
// skip if already set
if f.set[fl.Name] {
return
}

// check environment variables
for _, pfx := range prefixes {
name := strings.ToUpper(pfx + strings.Replace(fl.Name, ".", "_", -1))
if val, ok := env[name]; ok {
f.set[fl.Name] = true
f.Set(fl.Name, val)
return
}
}

// check properties
if p == nil {
return
}
if val, ok := p.Get(fl.Name); ok {
f.set[fl.Name] = true
f.Set(fl.Name, val)
return
}
})
return nil
}
62 changes: 62 additions & 0 deletions config/flagset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package config

import (
"flag"
"testing"

"github.com/magiconair/properties"
)

func TestParseFlags(t *testing.T) {
props := func(s string) *properties.Properties {
return properties.MustLoadString(s)
}

tests := []struct {
desc string
args []string
env []string
prefix []string
props string
v string
}{
{
desc: "cmdline should win",
args: []string{"-v", "cmdline"},
env: []string{"v=env"},
props: "v=props",
v: "cmdline",
},
{
desc: "env should win",
env: []string{"v=env"},
props: "v=props",
v: "env",
},
{
desc: "env with prefix should win",
env: []string{"v=env", "p_v=prefix"},
prefix: []string{"p_"},
props: "v=props",
v: "prefix",
},
{
desc: "props should win",
props: "v=props",
v: "props",
},
}

for i, tt := range tests {
var v string
f := NewFlagSet("test", flag.ExitOnError)
f.StringVar(&v, "v", "default", "")
err := f.ParseFlags(tt.args, tt.env, tt.prefix, props(tt.props))
if err != nil {
t.Errorf("%d -%s: got %v want nil", i, tt.desc, err)
}
if got, want := v, tt.v; got != want {
t.Errorf("%d - %s: got %q want %q", i, tt.desc, got, want)
}
}
}

0 comments on commit 86e74c7

Please sign in to comment.