From bbfc86eded00d3a5e30a3a3a0095807761b6a583 Mon Sep 17 00:00:00 2001 From: Alex Guerra Date: Thu, 30 Mar 2017 13:26:50 -0300 Subject: [PATCH] Increase deploy timeouts and make them configurable Also upgrade to the latest envconfig. --- CHANGELOG.md | 1 + Godeps/Godeps.json | 6 +- k8s/deployments.go | 12 +- .../kelseyhightower/envconfig/.travis.yml | 7 +- .../kelseyhightower/envconfig/README.md | 57 +- .../kelseyhightower/envconfig/env_os.go | 7 + .../kelseyhightower/envconfig/env_syscall.go | 7 + .../kelseyhightower/envconfig/envconfig.go | 221 ++++-- .../envconfig/envconfig_test.go | 698 ++++++++++++++++++ .../envconfig/testdata/custom.txt | 30 + .../envconfig/testdata/default_list.txt | 153 ++++ .../envconfig/testdata/default_table.txt | 34 + .../envconfig/testdata/fault.txt | 30 + .../kelseyhightower/envconfig/usage.go | 158 ++++ .../kelseyhightower/envconfig/usage_test.go | 155 ++++ 15 files changed, 1501 insertions(+), 75 deletions(-) create mode 100644 vendor/github.com/kelseyhightower/envconfig/env_os.go create mode 100644 vendor/github.com/kelseyhightower/envconfig/env_syscall.go create mode 100644 vendor/github.com/kelseyhightower/envconfig/envconfig_test.go create mode 100644 vendor/github.com/kelseyhightower/envconfig/testdata/custom.txt create mode 100644 vendor/github.com/kelseyhightower/envconfig/testdata/default_list.txt create mode 100644 vendor/github.com/kelseyhightower/envconfig/testdata/default_table.txt create mode 100644 vendor/github.com/kelseyhightower/envconfig/testdata/fault.txt create mode 100644 vendor/github.com/kelseyhightower/envconfig/usage.go create mode 100644 vendor/github.com/kelseyhightower/envconfig/usage_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f28a7484..23ce48cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Location of slugbuilder and slugrunner images - Read keys from k8s secrets +- Increase deploy timeouts and make them configurable ## [0.2.2] - 2017-03-16 ### Changed diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 7f4edd280..64c0aa03a 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,7 +1,7 @@ { "ImportPath": "github.com/luizalabs/teresa-api", "GoVersion": "go1.6", - "GodepVersion": "v62", + "GodepVersion": "v75", "Packages": [ "./..." ], @@ -367,8 +367,8 @@ }, { "ImportPath": "github.com/kelseyhightower/envconfig", - "Comment": "1.1.0-17-g91921eb", - "Rev": "91921eb4cf999321cdbeebdba5a03555800d493b" + "Comment": "v1.3.0-3-g8bf4bbf", + "Rev": "8bf4bbfc795e2c7c8a5ea47b707453ed019e2ad4" }, { "ImportPath": "github.com/mailru/easyjson/buffer", diff --git a/k8s/deployments.go b/k8s/deployments.go index c6221ca88..32dde08a8 100644 --- a/k8s/deployments.go +++ b/k8s/deployments.go @@ -59,7 +59,9 @@ type deploy struct { } type DeploymentConfig struct { - RevisionHistoryLimit int32 `envconfig:"revision_history_limit" default:"5"` + FinishTimeout time.Duration `split_words:"true" default:"30m"` + RevisionHistoryLimit int32 `split_words:"true" default:"5"` + StartTimeout time.Duration `split_words:"true" default:"10m"` } var deploymentConfig DeploymentConfig @@ -166,15 +168,14 @@ func (c deployments) Get(appName string) (d *extensions.Deployment, err error) { // buildApp creates a builder POD to build the App, waits the POD to be completed func (c deployments) buildApp(app *models.App, deploy *deploy, storage helpers.Storage, w io.Writer) error { - // TODO: fix times for wait start and wait end - // creating builder POD pod, err := c.createBuilderPod(app, deploy, storage) if err != nil { return err } // wainting POD to start the builder proccess - if err = c.waitPodStart(pod, 1*time.Second, 2*time.Minute); err != nil { + err = c.waitPodStart(pod, 1*time.Second, deploymentConfig.StartTimeout) + if err != nil { return err } opts := &api.PodLogOptions{ @@ -188,7 +189,8 @@ func (c deployments) buildApp(app *models.App, deploy *deploy, storage helpers.S defer s.Close() io.Copy(w, s) // wait POD finish - if err = c.waitPodEnd(pod, 1*time.Second, 2*time.Minute); err != nil { + err = c.waitPodEnd(pod, 1*time.Second, deploymentConfig.FinishTimeout) + if err != nil { return err } // get POD exit code. diff --git a/vendor/github.com/kelseyhightower/envconfig/.travis.yml b/vendor/github.com/kelseyhightower/envconfig/.travis.yml index e15301a59..00e9aca35 100644 --- a/vendor/github.com/kelseyhightower/envconfig/.travis.yml +++ b/vendor/github.com/kelseyhightower/envconfig/.travis.yml @@ -1,7 +1,8 @@ language: go go: - - 1.4 - - 1.5 - - 1.6 + - 1.4.3 + - 1.5.4 + - 1.6.4 + - 1.7.5 - tip diff --git a/vendor/github.com/kelseyhightower/envconfig/README.md b/vendor/github.com/kelseyhightower/envconfig/README.md index dd516a21b..6c85a5cbb 100644 --- a/vendor/github.com/kelseyhightower/envconfig/README.md +++ b/vendor/github.com/kelseyhightower/envconfig/README.md @@ -1,6 +1,6 @@ # envconfig -[![Build Status](https://travis-ci.org/kelseyhightower/envconfig.png)](https://travis-ci.org/kelseyhightower/envconfig) +[![Build Status](https://travis-ci.org/kelseyhightower/envconfig.svg)](https://travis-ci.org/kelseyhightower/envconfig) ```Go import "github.com/kelseyhightower/envconfig" @@ -21,6 +21,7 @@ export MYAPP_USER=Kelsey export MYAPP_RATE="0.5" export MYAPP_TIMEOUT="3m" export MYAPP_USERS="rob,ken,robert" +export MYAPP_COLORCODES="red:1,green:2,blue:3" ``` Write some code: @@ -37,12 +38,13 @@ import ( ) type Specification struct { - Debug bool - Port int - User string - Users []string - Rate float32 - Timeout time.Duration + Debug bool + Port int + User string + Users []string + Rate float32 + Timeout time.Duration + ColorCodes map[string]int } func main() { @@ -61,6 +63,11 @@ func main() { for _, u := range s.Users { fmt.Printf(" %s\n", u) } + + fmt.Println("Color codes:") + for k, v := range s.ColorCodes { + fmt.Printf(" %s: %d\n", k, v) + } } ``` @@ -76,6 +83,10 @@ Users: rob ken robert +Color codes: + red: 1 + green: 2 + blue: 3 ``` ## Struct Tag Support @@ -87,20 +98,30 @@ For example, consider the following struct: ```Go type Specification struct { - MultiWordVar string `envconfig:"multi_word_var"` - DefaultVar string `default:"foobar"` - RequiredVar string `required:"true"` - IgnoredVar string `ignored:"true"` + ManualOverride1 string `envconfig:"manual_override_1"` + DefaultVar string `default:"foobar"` + RequiredVar string `required:"true"` + IgnoredVar string `ignored:"true"` + AutoSplitVar string `split_words:"true"` } ``` -Envconfig will process value for `MultiWordVar` by populating it with the -value for `MYAPP_MULTI_WORD_VAR`. +Envconfig has automatic support for CamelCased struct elements when the +`split_words:"true"` tag is supplied. Without this tag, `AutoSplitVar` above +would look for an environment variable called `MYAPP_AUTOSPLITVAR`. With the +setting applied it will look for `MYAPP_AUTO_SPLIT_VAR`. Note that numbers +will get globbed into the previous word. If the setting does not do the +right thing, you may use a manual override. + +Envconfig will process value for `ManualOverride1` by populating it with the +value for `MYAPP_MANUAL_OVERRIDE_1`. Without this struct tag, it would have +instead looked up `MYAPP_MANUALOVERRIDE1`. With the `split_words:"true"` tag +it would have looked up `MYAPP_MANUAL_OVERRIDE1`. ```Bash -export MYAPP_MULTI_WORD_VAR="this will be the value" +export MYAPP_MANUAL_OVERRIDE_1="this will be the value" -# export MYAPP_MULTIWORDVAR="and this will not" +# export MYAPP_MANUALOVERRIDE1="and this will not" ``` If envconfig can't find an environment variable value for `MYAPP_DEFAULTVAR`, @@ -135,6 +156,9 @@ envconfig supports supports these struct field types: * int8, int16, int32, int64 * bool * float32, float64 + * slices of any supported type + * maps (keys and values of any supported type) + * [encoding.TextUnmarshaler](https://golang.org/pkg/encoding/#TextUnmarshaler) Embedded structs using these fields are also supported. @@ -159,3 +183,6 @@ type DNSConfig struct { Address IPDecoder `envconfig:"DNS_SERVER"` } ``` + +Also, envconfig will use a `Set(string) error` method like from the +[flag.Value](https://godoc.org/flag#Value) interface if implemented. diff --git a/vendor/github.com/kelseyhightower/envconfig/env_os.go b/vendor/github.com/kelseyhightower/envconfig/env_os.go new file mode 100644 index 000000000..a6a014a2b --- /dev/null +++ b/vendor/github.com/kelseyhightower/envconfig/env_os.go @@ -0,0 +1,7 @@ +// +build appengine + +package envconfig + +import "os" + +var lookupEnv = os.LookupEnv diff --git a/vendor/github.com/kelseyhightower/envconfig/env_syscall.go b/vendor/github.com/kelseyhightower/envconfig/env_syscall.go new file mode 100644 index 000000000..9d98085b9 --- /dev/null +++ b/vendor/github.com/kelseyhightower/envconfig/env_syscall.go @@ -0,0 +1,7 @@ +// +build !appengine + +package envconfig + +import "syscall" + +var lookupEnv = syscall.Getenv diff --git a/vendor/github.com/kelseyhightower/envconfig/envconfig.go b/vendor/github.com/kelseyhightower/envconfig/envconfig.go index 1daf38987..a2e00b4f4 100644 --- a/vendor/github.com/kelseyhightower/envconfig/envconfig.go +++ b/vendor/github.com/kelseyhightower/envconfig/envconfig.go @@ -5,12 +5,13 @@ package envconfig import ( + "encoding" "errors" "fmt" "reflect" + "regexp" "strconv" "strings" - "syscall" "time" ) @@ -24,83 +25,164 @@ type ParseError struct { FieldName string TypeName string Value string + Err error } -// A Decoder is a type that knows how to de-serialize environment variables -// into itself. +// Decoder has the same semantics as Setter, but takes higher precedence. +// It is provided for historical compatibility. type Decoder interface { Decode(value string) error } +// Setter is implemented by types can self-deserialize values. +// Any type that implements flag.Value also implements Setter. +type Setter interface { + Set(value string) error +} + func (e *ParseError) Error() string { - return fmt.Sprintf("envconfig.Process: assigning %[1]s to %[2]s: converting '%[3]s' to type %[4]s", e.KeyName, e.FieldName, e.Value, e.TypeName) + return fmt.Sprintf("envconfig.Process: assigning %[1]s to %[2]s: converting '%[3]s' to type %[4]s. details: %[5]s", e.KeyName, e.FieldName, e.Value, e.TypeName, e.Err) } -// Process populates the specified struct based on environment variables -func Process(prefix string, spec interface{}) error { +// varInfo maintains information about the configuration variable +type varInfo struct { + Name string + Alt string + Key string + Field reflect.Value + Tags reflect.StructTag +} + +// GatherInfo gathers information about the specified struct +func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) { + expr := regexp.MustCompile("([^A-Z]+|[A-Z][^A-Z]+|[A-Z]+)") s := reflect.ValueOf(spec) if s.Kind() != reflect.Ptr { - return ErrInvalidSpecification + return nil, ErrInvalidSpecification } s = s.Elem() if s.Kind() != reflect.Struct { - return ErrInvalidSpecification + return nil, ErrInvalidSpecification } typeOfSpec := s.Type() + + // over allocate an info array, we will extend if needed later + infos := make([]varInfo, 0, s.NumField()) for i := 0; i < s.NumField(); i++ { f := s.Field(i) - if !f.CanSet() || typeOfSpec.Field(i).Tag.Get("ignored") == "true" { + ftype := typeOfSpec.Field(i) + if !f.CanSet() || isTrue(ftype.Tag.Get("ignored")) { continue } - if typeOfSpec.Field(i).Anonymous && f.Kind() == reflect.Struct { - embeddedPtr := f.Addr().Interface() - if err := Process(prefix, embeddedPtr); err != nil { - return err + for f.Kind() == reflect.Ptr { + if f.IsNil() { + if f.Type().Elem().Kind() != reflect.Struct { + // nil pointer to a non-struct: leave it alone + break + } + // nil pointer to struct: create a zero instance + f.Set(reflect.New(f.Type().Elem())) + } + f = f.Elem() + } + + // Capture information about the config variable + info := varInfo{ + Name: ftype.Name, + Field: f, + Tags: ftype.Tag, + Alt: strings.ToUpper(ftype.Tag.Get("envconfig")), + } + + // Default to the field name as the env var name (will be upcased) + info.Key = info.Name + + // Best effort to un-pick camel casing as separate words + if isTrue(ftype.Tag.Get("split_words")) { + words := expr.FindAllStringSubmatch(ftype.Name, -1) + if len(words) > 0 { + var name []string + for _, words := range words { + name = append(name, words[0]) + } + + info.Key = strings.Join(name, "_") } - f.Set(reflect.ValueOf(embeddedPtr).Elem()) } + if info.Alt != "" { + info.Key = info.Alt + } + if prefix != "" { + info.Key = fmt.Sprintf("%s_%s", prefix, info.Key) + } + info.Key = strings.ToUpper(info.Key) + infos = append(infos, info) - alt := typeOfSpec.Field(i).Tag.Get("envconfig") - fieldName := typeOfSpec.Field(i).Name - if alt != "" { - fieldName = alt + if f.Kind() == reflect.Struct { + // honor Decode if present + if decoderFrom(f) == nil && setterFrom(f) == nil && textUnmarshaler(f) == nil { + innerPrefix := prefix + if !ftype.Anonymous { + innerPrefix = info.Key + } + + embeddedPtr := f.Addr().Interface() + embeddedInfos, err := gatherInfo(innerPrefix, embeddedPtr) + if err != nil { + return nil, err + } + infos = append(infos[:len(infos)-1], embeddedInfos...) + + continue + } } - key := strings.ToUpper(fmt.Sprintf("%s_%s", prefix, fieldName)) + } + return infos, nil +} + +// Process populates the specified struct based on environment variables +func Process(prefix string, spec interface{}) error { + infos, err := gatherInfo(prefix, spec) + + for _, info := range infos { + // `os.Getenv` cannot differentiate between an explicitly set empty value // and an unset value. `os.LookupEnv` is preferred to `syscall.Getenv`, - // but it is only available in go1.5 or newer. - value, ok := syscall.Getenv(key) - if !ok && alt != "" { - key := strings.ToUpper(fieldName) - value, ok = syscall.Getenv(key) + // but it is only available in go1.5 or newer. We're using Go build tags + // here to use os.LookupEnv for >=go1.5 + value, ok := lookupEnv(info.Key) + if !ok && info.Alt != "" { + value, ok = lookupEnv(info.Alt) } - def := typeOfSpec.Field(i).Tag.Get("default") + def := info.Tags.Get("default") if def != "" && !ok { value = def } - req := typeOfSpec.Field(i).Tag.Get("required") + req := info.Tags.Get("required") if !ok && def == "" { - if req == "true" { - return fmt.Errorf("required key %s missing value", key) + if isTrue(req) { + return fmt.Errorf("required key %s missing value", info.Key) } continue } - err := processField(value, f) + err := processField(value, info.Field) if err != nil { return &ParseError{ - KeyName: key, - FieldName: fieldName, - TypeName: f.Type().String(), + KeyName: info.Key, + FieldName: info.Name, + TypeName: info.Field.Type().String(), Value: value, + Err: err, } } } - return nil + + return err } // MustProcess is the same as Process but panics if an error occurs @@ -117,6 +199,15 @@ func processField(value string, field reflect.Value) error { if decoder != nil { return decoder.Decode(value) } + // look for Set method if Decode not defined + setter := setterFrom(field) + if setter != nil { + return setter.Set(value) + } + + if t := textUnmarshaler(field); t != nil { + return t.UnmarshalText([]byte(value)) + } if typ.Kind() == reflect.Ptr { typ = typ.Elem() @@ -174,28 +265,60 @@ func processField(value string, field reflect.Value) error { } } field.Set(sl) + case reflect.Map: + pairs := strings.Split(value, ",") + mp := reflect.MakeMap(typ) + for _, pair := range pairs { + kvpair := strings.Split(pair, ":") + if len(kvpair) != 2 { + return fmt.Errorf("invalid map item: %q", pair) + } + k := reflect.New(typ.Key()).Elem() + err := processField(kvpair[0], k) + if err != nil { + return err + } + v := reflect.New(typ.Elem()).Elem() + err = processField(kvpair[1], v) + if err != nil { + return err + } + mp.SetMapIndex(k, v) + } + field.Set(mp) } return nil } -func decoderFrom(field reflect.Value) Decoder { - if field.CanInterface() { - dec, ok := field.Interface().(Decoder) - if ok { - return dec - } +func interfaceFrom(field reflect.Value, fn func(interface{}, *bool)) { + // it may be impossible for a struct field to fail this check + if !field.CanInterface() { + return } - - // also check if pointer-to-type implements Decoder, - // and we can get a pointer to our field - if field.CanAddr() { - field = field.Addr() - dec, ok := field.Interface().(Decoder) - if ok { - return dec - } + var ok bool + fn(field.Interface(), &ok) + if !ok && field.CanAddr() { + fn(field.Addr().Interface(), &ok) } +} - return nil +func decoderFrom(field reflect.Value) (d Decoder) { + interfaceFrom(field, func(v interface{}, ok *bool) { d, *ok = v.(Decoder) }) + return d +} + +func setterFrom(field reflect.Value) (s Setter) { + interfaceFrom(field, func(v interface{}, ok *bool) { s, *ok = v.(Setter) }) + return s +} + +func textUnmarshaler(field reflect.Value) (t encoding.TextUnmarshaler) { + interfaceFrom(field, func(v interface{}, ok *bool) { t, *ok = v.(encoding.TextUnmarshaler) }) + return t +} + +func isTrue(s string) bool { + b, _ := strconv.ParseBool(s) + return b } diff --git a/vendor/github.com/kelseyhightower/envconfig/envconfig_test.go b/vendor/github.com/kelseyhightower/envconfig/envconfig_test.go new file mode 100644 index 000000000..afa624b30 --- /dev/null +++ b/vendor/github.com/kelseyhightower/envconfig/envconfig_test.go @@ -0,0 +1,698 @@ +// Copyright (c) 2013 Kelsey Hightower. All rights reserved. +// Use of this source code is governed by the MIT License that can be found in +// the LICENSE file. + +package envconfig + +import ( + "flag" + "fmt" + "os" + "testing" + "time" +) + +type HonorDecodeInStruct struct { + Value string +} + +func (h *HonorDecodeInStruct) Decode(env string) error { + h.Value = "decoded" + return nil +} + +type Specification struct { + Embedded `desc:"can we document a struct"` + EmbeddedButIgnored `ignored:"true"` + Debug bool + Port int + Rate float32 + User string + TTL uint32 + Timeout time.Duration + AdminUsers []string + MagicNumbers []int + ColorCodes map[string]int + MultiWordVar string + MultiWordVarWithAutoSplit uint32 `split_words:"true"` + SomePointer *string + SomePointerWithDefault *string `default:"foo2baz" desc:"foorbar is the word"` + MultiWordVarWithAlt string `envconfig:"MULTI_WORD_VAR_WITH_ALT" desc:"what alt"` + MultiWordVarWithLowerCaseAlt string `envconfig:"multi_word_var_with_lower_case_alt"` + NoPrefixWithAlt string `envconfig:"SERVICE_HOST"` + DefaultVar string `default:"foobar"` + RequiredVar string `required:"True"` + NoPrefixDefault string `envconfig:"BROKER" default:"127.0.0.1"` + RequiredDefault string `required:"true" default:"foo2bar"` + Ignored string `ignored:"true"` + NestedSpecification struct { + Property string `envconfig:"inner"` + PropertyWithDefault string `default:"fuzzybydefault"` + } `envconfig:"outer"` + AfterNested string + DecodeStruct HonorDecodeInStruct `envconfig:"honor"` + Datetime time.Time +} + +type Embedded struct { + Enabled bool `desc:"some embedded value"` + EmbeddedPort int + MultiWordVar string + MultiWordVarWithAlt string `envconfig:"MULTI_WITH_DIFFERENT_ALT"` + EmbeddedAlt string `envconfig:"EMBEDDED_WITH_ALT"` + EmbeddedIgnored string `ignored:"true"` +} + +type EmbeddedButIgnored struct { + FirstEmbeddedButIgnored string + SecondEmbeddedButIgnored string +} + +func TestProcess(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_DEBUG", "true") + os.Setenv("ENV_CONFIG_PORT", "8080") + os.Setenv("ENV_CONFIG_RATE", "0.5") + os.Setenv("ENV_CONFIG_USER", "Kelsey") + os.Setenv("ENV_CONFIG_TIMEOUT", "2m") + os.Setenv("ENV_CONFIG_ADMINUSERS", "John,Adam,Will") + os.Setenv("ENV_CONFIG_MAGICNUMBERS", "5,10,20") + os.Setenv("ENV_CONFIG_COLORCODES", "red:1,green:2,blue:3") + os.Setenv("SERVICE_HOST", "127.0.0.1") + os.Setenv("ENV_CONFIG_TTL", "30") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + os.Setenv("ENV_CONFIG_IGNORED", "was-not-ignored") + os.Setenv("ENV_CONFIG_OUTER_INNER", "iamnested") + os.Setenv("ENV_CONFIG_AFTERNESTED", "after") + os.Setenv("ENV_CONFIG_HONOR", "honor") + os.Setenv("ENV_CONFIG_DATETIME", "2016-08-16T18:57:05Z") + os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT", "24") + err := Process("env_config", &s) + if err != nil { + t.Error(err.Error()) + } + if s.NoPrefixWithAlt != "127.0.0.1" { + t.Errorf("expected %v, got %v", "127.0.0.1", s.NoPrefixWithAlt) + } + if !s.Debug { + t.Errorf("expected %v, got %v", true, s.Debug) + } + if s.Port != 8080 { + t.Errorf("expected %d, got %v", 8080, s.Port) + } + if s.Rate != 0.5 { + t.Errorf("expected %f, got %v", 0.5, s.Rate) + } + if s.TTL != 30 { + t.Errorf("expected %d, got %v", 30, s.TTL) + } + if s.User != "Kelsey" { + t.Errorf("expected %s, got %s", "Kelsey", s.User) + } + if s.Timeout != 2*time.Minute { + t.Errorf("expected %s, got %s", 2*time.Minute, s.Timeout) + } + if s.RequiredVar != "foo" { + t.Errorf("expected %s, got %s", "foo", s.RequiredVar) + } + if len(s.AdminUsers) != 3 || + s.AdminUsers[0] != "John" || + s.AdminUsers[1] != "Adam" || + s.AdminUsers[2] != "Will" { + t.Errorf("expected %#v, got %#v", []string{"John", "Adam", "Will"}, s.AdminUsers) + } + if len(s.MagicNumbers) != 3 || + s.MagicNumbers[0] != 5 || + s.MagicNumbers[1] != 10 || + s.MagicNumbers[2] != 20 { + t.Errorf("expected %#v, got %#v", []int{5, 10, 20}, s.MagicNumbers) + } + if s.Ignored != "" { + t.Errorf("expected empty string, got %#v", s.Ignored) + } + + if len(s.ColorCodes) != 3 || + s.ColorCodes["red"] != 1 || + s.ColorCodes["green"] != 2 || + s.ColorCodes["blue"] != 3 { + t.Errorf( + "expected %#v, got %#v", + map[string]int{ + "red": 1, + "green": 2, + "blue": 3, + }, + s.ColorCodes, + ) + } + + if s.NestedSpecification.Property != "iamnested" { + t.Errorf("expected '%s' string, got %#v", "iamnested", s.NestedSpecification.Property) + } + + if s.NestedSpecification.PropertyWithDefault != "fuzzybydefault" { + t.Errorf("expected default '%s' string, got %#v", "fuzzybydefault", s.NestedSpecification.PropertyWithDefault) + } + + if s.AfterNested != "after" { + t.Errorf("expected default '%s' string, got %#v", "after", s.AfterNested) + } + + if s.DecodeStruct.Value != "decoded" { + t.Errorf("expected default '%s' string, got %#v", "decoded", s.DecodeStruct.Value) + } + + if expected := time.Date(2016, 8, 16, 18, 57, 05, 0, time.UTC); !s.Datetime.Equal(expected) { + t.Errorf("expected %s, got %s", expected.Format(time.RFC3339), s.Datetime.Format(time.RFC3339)) + } + + if s.MultiWordVarWithAutoSplit != 24 { + t.Errorf("expected %q, got %q", 24, s.MultiWordVarWithAutoSplit) + } +} + +func TestParseErrorBool(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_DEBUG", "string") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + err := Process("env_config", &s) + v, ok := err.(*ParseError) + if !ok { + t.Errorf("expected ParseError, got %v", v) + } + if v.FieldName != "Debug" { + t.Errorf("expected %s, got %v", "Debug", v.FieldName) + } + if s.Debug != false { + t.Errorf("expected %v, got %v", false, s.Debug) + } +} + +func TestParseErrorFloat32(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_RATE", "string") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + err := Process("env_config", &s) + v, ok := err.(*ParseError) + if !ok { + t.Errorf("expected ParseError, got %v", v) + } + if v.FieldName != "Rate" { + t.Errorf("expected %s, got %v", "Rate", v.FieldName) + } + if s.Rate != 0 { + t.Errorf("expected %v, got %v", 0, s.Rate) + } +} + +func TestParseErrorInt(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_PORT", "string") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + err := Process("env_config", &s) + v, ok := err.(*ParseError) + if !ok { + t.Errorf("expected ParseError, got %v", v) + } + if v.FieldName != "Port" { + t.Errorf("expected %s, got %v", "Port", v.FieldName) + } + if s.Port != 0 { + t.Errorf("expected %v, got %v", 0, s.Port) + } +} + +func TestParseErrorUint(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_TTL", "-30") + err := Process("env_config", &s) + v, ok := err.(*ParseError) + if !ok { + t.Errorf("expected ParseError, got %v", v) + } + if v.FieldName != "TTL" { + t.Errorf("expected %s, got %v", "TTL", v.FieldName) + } + if s.TTL != 0 { + t.Errorf("expected %v, got %v", 0, s.TTL) + } +} + +func TestParseErrorSplitWords(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT", "shakespeare") + err := Process("env_config", &s) + v, ok := err.(*ParseError) + if !ok { + t.Errorf("expected ParseError, got %v", v) + } + if v.FieldName != "MultiWordVarWithAutoSplit" { + t.Errorf("expected %s, got %v", "", v.FieldName) + } + if s.MultiWordVarWithAutoSplit != 0 { + t.Errorf("expected %v, got %v", 0, s.MultiWordVarWithAutoSplit) + } +} + +func TestErrInvalidSpecification(t *testing.T) { + m := make(map[string]string) + err := Process("env_config", &m) + if err != ErrInvalidSpecification { + t.Errorf("expected %v, got %v", ErrInvalidSpecification, err) + } +} + +func TestUnsetVars(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("USER", "foo") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + // If the var is not defined the non-prefixed version should not be used + // unless the struct tag says so + if s.User != "" { + t.Errorf("expected %q, got %q", "", s.User) + } +} + +func TestAlternateVarNames(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_MULTI_WORD_VAR", "foo") + os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT", "bar") + os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT", "baz") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + // Setting the alt version of the var in the environment has no effect if + // the struct tag is not supplied + if s.MultiWordVar != "" { + t.Errorf("expected %q, got %q", "", s.MultiWordVar) + } + + // Setting the alt version of the var in the environment correctly sets + // the value if the struct tag IS supplied + if s.MultiWordVarWithAlt != "bar" { + t.Errorf("expected %q, got %q", "bar", s.MultiWordVarWithAlt) + } + + // Alt value is not case sensitive and is treated as all uppercase + if s.MultiWordVarWithLowerCaseAlt != "baz" { + t.Errorf("expected %q, got %q", "baz", s.MultiWordVarWithLowerCaseAlt) + } +} + +func TestRequiredVar(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foobar") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if s.RequiredVar != "foobar" { + t.Errorf("expected %s, got %s", "foobar", s.RequiredVar) + } +} + +func TestRequiredMissing(t *testing.T) { + var s Specification + os.Clearenv() + + err := Process("env_config", &s) + if err == nil { + t.Error("no failure when missing required variable") + } +} + +func TestBlankDefaultVar(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "requiredvalue") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if s.DefaultVar != "foobar" { + t.Errorf("expected %s, got %s", "foobar", s.DefaultVar) + } + + if *s.SomePointerWithDefault != "foo2baz" { + t.Errorf("expected %s, got %s", "foo2baz", *s.SomePointerWithDefault) + } +} + +func TestNonBlankDefaultVar(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_DEFAULTVAR", "nondefaultval") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "requiredvalue") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if s.DefaultVar != "nondefaultval" { + t.Errorf("expected %s, got %s", "nondefaultval", s.DefaultVar) + } +} + +func TestExplicitBlankDefaultVar(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_DEFAULTVAR", "") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "") + + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if s.DefaultVar != "" { + t.Errorf("expected %s, got %s", "\"\"", s.DefaultVar) + } +} + +func TestAlternateNameDefaultVar(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("BROKER", "betterbroker") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if s.NoPrefixDefault != "betterbroker" { + t.Errorf("expected %q, got %q", "betterbroker", s.NoPrefixDefault) + } + + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if s.NoPrefixDefault != "127.0.0.1" { + t.Errorf("expected %q, got %q", "127.0.0.1", s.NoPrefixDefault) + } +} + +func TestRequiredDefault(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if s.RequiredDefault != "foo2bar" { + t.Errorf("expected %q, got %q", "foo2bar", s.RequiredDefault) + } +} + +func TestPointerFieldBlank(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if s.SomePointer != nil { + t.Errorf("expected , got %q", *s.SomePointer) + } +} + +func TestMustProcess(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_DEBUG", "true") + os.Setenv("ENV_CONFIG_PORT", "8080") + os.Setenv("ENV_CONFIG_RATE", "0.5") + os.Setenv("ENV_CONFIG_USER", "Kelsey") + os.Setenv("SERVICE_HOST", "127.0.0.1") + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + MustProcess("env_config", &s) + + defer func() { + if err := recover(); err != nil { + return + } + + t.Error("expected panic") + }() + m := make(map[string]string) + MustProcess("env_config", &m) +} + +func TestEmbeddedStruct(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "required") + os.Setenv("ENV_CONFIG_ENABLED", "true") + os.Setenv("ENV_CONFIG_EMBEDDEDPORT", "1234") + os.Setenv("ENV_CONFIG_MULTIWORDVAR", "foo") + os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT", "bar") + os.Setenv("ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT", "baz") + os.Setenv("ENV_CONFIG_EMBEDDED_WITH_ALT", "foobar") + os.Setenv("ENV_CONFIG_SOMEPOINTER", "foobaz") + os.Setenv("ENV_CONFIG_EMBEDDED_IGNORED", "was-not-ignored") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + if !s.Enabled { + t.Errorf("expected %v, got %v", true, s.Enabled) + } + if s.EmbeddedPort != 1234 { + t.Errorf("expected %d, got %v", 1234, s.EmbeddedPort) + } + if s.MultiWordVar != "foo" { + t.Errorf("expected %s, got %s", "foo", s.MultiWordVar) + } + if s.Embedded.MultiWordVar != "foo" { + t.Errorf("expected %s, got %s", "foo", s.Embedded.MultiWordVar) + } + if s.MultiWordVarWithAlt != "bar" { + t.Errorf("expected %s, got %s", "bar", s.MultiWordVarWithAlt) + } + if s.Embedded.MultiWordVarWithAlt != "baz" { + t.Errorf("expected %s, got %s", "baz", s.Embedded.MultiWordVarWithAlt) + } + if s.EmbeddedAlt != "foobar" { + t.Errorf("expected %s, got %s", "foobar", s.EmbeddedAlt) + } + if *s.SomePointer != "foobaz" { + t.Errorf("expected %s, got %s", "foobaz", *s.SomePointer) + } + if s.EmbeddedIgnored != "" { + t.Errorf("expected empty string, got %#v", s.Ignored) + } +} + +func TestEmbeddedButIgnoredStruct(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "required") + os.Setenv("ENV_CONFIG_FIRSTEMBEDDEDBUTIGNORED", "was-not-ignored") + os.Setenv("ENV_CONFIG_SECONDEMBEDDEDBUTIGNORED", "was-not-ignored") + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + if s.FirstEmbeddedButIgnored != "" { + t.Errorf("expected empty string, got %#v", s.Ignored) + } + if s.SecondEmbeddedButIgnored != "" { + t.Errorf("expected empty string, got %#v", s.Ignored) + } +} + +func TestNonPointerFailsProperly(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "snap") + + err := Process("env_config", s) + if err != ErrInvalidSpecification { + t.Errorf("non-pointer should fail with ErrInvalidSpecification, was instead %s", err) + } +} + +func TestCustomValueFields(t *testing.T) { + var s struct { + Foo string + Bar bracketed + Baz quoted + Struct setterStruct + } + + // Set would panic when the receiver is nil, + // so make sure it has an initial value to replace. + s.Baz = quoted{new(bracketed)} + + os.Clearenv() + os.Setenv("ENV_CONFIG_FOO", "foo") + os.Setenv("ENV_CONFIG_BAR", "bar") + os.Setenv("ENV_CONFIG_BAZ", "baz") + os.Setenv("ENV_CONFIG_STRUCT", "inner") + + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if want := "foo"; s.Foo != want { + t.Errorf("foo: got %#q, want %#q", s.Foo, want) + } + + if want := "[bar]"; s.Bar.String() != want { + t.Errorf("bar: got %#q, want %#q", s.Bar, want) + } + + if want := `["baz"]`; s.Baz.String() != want { + t.Errorf(`baz: got %#q, want %#q`, s.Baz, want) + } + + if want := `setterstruct{"inner"}`; s.Struct.Inner != want { + t.Errorf(`Struct.Inner: got %#q, want %#q`, s.Struct.Inner, want) + } +} + +func TestCustomPointerFields(t *testing.T) { + var s struct { + Foo string + Bar *bracketed + Baz *quoted + Struct *setterStruct + } + + // Set would panic when the receiver is nil, + // so make sure they have initial values to replace. + s.Bar = new(bracketed) + s.Baz = "ed{new(bracketed)} + + os.Clearenv() + os.Setenv("ENV_CONFIG_FOO", "foo") + os.Setenv("ENV_CONFIG_BAR", "bar") + os.Setenv("ENV_CONFIG_BAZ", "baz") + os.Setenv("ENV_CONFIG_STRUCT", "inner") + + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + + if want := "foo"; s.Foo != want { + t.Errorf("foo: got %#q, want %#q", s.Foo, want) + } + + if want := "[bar]"; s.Bar.String() != want { + t.Errorf("bar: got %#q, want %#q", s.Bar, want) + } + + if want := `["baz"]`; s.Baz.String() != want { + t.Errorf(`baz: got %#q, want %#q`, s.Baz, want) + } + + if want := `setterstruct{"inner"}`; s.Struct.Inner != want { + t.Errorf(`Struct.Inner: got %#q, want %#q`, s.Struct.Inner, want) + } +} + +func TestEmptyPrefixUsesFieldNames(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("REQUIREDVAR", "foo") + + err := Process("", &s) + if err != nil { + t.Errorf("Process failed: %s", err) + } + + if s.RequiredVar != "foo" { + t.Errorf( + `RequiredVar not populated correctly: expected "foo", got %q`, + s.RequiredVar, + ) + } +} + +func TestNestedStructVarName(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "required") + val := "found with only short name" + os.Setenv("INNER", val) + if err := Process("env_config", &s); err != nil { + t.Error(err.Error()) + } + if s.NestedSpecification.Property != val { + t.Errorf("expected %s, got %s", val, s.NestedSpecification.Property) + } +} + +func TestTextUnmarshalerError(t *testing.T) { + var s Specification + os.Clearenv() + os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") + os.Setenv("ENV_CONFIG_DATETIME", "I'M NOT A DATE") + + err := Process("env_config", &s) + + v, ok := err.(*ParseError) + if !ok { + t.Errorf("expected ParseError, got %v", v) + } + if v.FieldName != "Datetime" { + t.Errorf("expected %s, got %v", "Debug", v.FieldName) + } + + expectedLowLevelError := time.ParseError{ + Layout: time.RFC3339, + Value: "I'M NOT A DATE", + LayoutElem: "2006", + ValueElem: "I'M NOT A DATE", + } + + if v.Err.Error() != expectedLowLevelError.Error() { + t.Errorf("expected %s, got %s", expectedLowLevelError, v.Err) + } + if s.Debug != false { + t.Errorf("expected %v, got %v", false, s.Debug) + } +} + +type bracketed string + +func (b *bracketed) Set(value string) error { + *b = bracketed("[" + value + "]") + return nil +} + +func (b bracketed) String() string { + return string(b) +} + +// quoted is used to test the precedence of Decode over Set. +// The sole field is a flag.Value rather than a setter to validate that +// all flag.Value implementations are also Setter implementations. +type quoted struct{ flag.Value } + +func (d quoted) Decode(value string) error { + return d.Set(`"` + value + `"`) +} + +type setterStruct struct { + Inner string +} + +func (ss *setterStruct) Set(value string) error { + ss.Inner = fmt.Sprintf("setterstruct{%q}", value) + return nil +} diff --git a/vendor/github.com/kelseyhightower/envconfig/testdata/custom.txt b/vendor/github.com/kelseyhightower/envconfig/testdata/custom.txt new file mode 100644 index 000000000..243e82c4c --- /dev/null +++ b/vendor/github.com/kelseyhightower/envconfig/testdata/custom.txt @@ -0,0 +1,30 @@ +ENV_CONFIG_ENABLED=some.embedded.value +ENV_CONFIG_EMBEDDEDPORT= +ENV_CONFIG_MULTIWORDVAR= +ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT= +ENV_CONFIG_EMBEDDED_WITH_ALT= +ENV_CONFIG_DEBUG= +ENV_CONFIG_PORT= +ENV_CONFIG_RATE= +ENV_CONFIG_USER= +ENV_CONFIG_TTL= +ENV_CONFIG_TIMEOUT= +ENV_CONFIG_ADMINUSERS= +ENV_CONFIG_MAGICNUMBERS= +ENV_CONFIG_COLORCODES= +ENV_CONFIG_MULTIWORDVAR= +ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT= +ENV_CONFIG_SOMEPOINTER= +ENV_CONFIG_SOMEPOINTERWITHDEFAULT=foorbar.is.the.word +ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT=what.alt +ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT= +ENV_CONFIG_SERVICE_HOST= +ENV_CONFIG_DEFAULTVAR= +ENV_CONFIG_REQUIREDVAR= +ENV_CONFIG_BROKER= +ENV_CONFIG_REQUIREDDEFAULT= +ENV_CONFIG_OUTER_INNER= +ENV_CONFIG_OUTER_PROPERTYWITHDEFAULT= +ENV_CONFIG_AFTERNESTED= +ENV_CONFIG_HONOR= +ENV_CONFIG_DATETIME= diff --git a/vendor/github.com/kelseyhightower/envconfig/testdata/default_list.txt b/vendor/github.com/kelseyhightower/envconfig/testdata/default_list.txt new file mode 100644 index 000000000..bc29211b0 --- /dev/null +++ b/vendor/github.com/kelseyhightower/envconfig/testdata/default_list.txt @@ -0,0 +1,153 @@ +This.application.is.configured.via.the.environment..The.following.environment +variables.can.be.used: + +ENV_CONFIG_ENABLED +..[description].some.embedded.value +..[type]........True.or.False +..[default]..... +..[required].... +ENV_CONFIG_EMBEDDEDPORT +..[description]. +..[type]........Integer +..[default]..... +..[required].... +ENV_CONFIG_MULTIWORDVAR +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_EMBEDDED_WITH_ALT +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_DEBUG +..[description]. +..[type]........True.or.False +..[default]..... +..[required].... +ENV_CONFIG_PORT +..[description]. +..[type]........Integer +..[default]..... +..[required].... +ENV_CONFIG_RATE +..[description]. +..[type]........Float +..[default]..... +..[required].... +ENV_CONFIG_USER +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_TTL +..[description]. +..[type]........Unsigned.Integer +..[default]..... +..[required].... +ENV_CONFIG_TIMEOUT +..[description]. +..[type]........Duration +..[default]..... +..[required].... +ENV_CONFIG_ADMINUSERS +..[description]. +..[type]........Comma-separated.list.of.String +..[default]..... +..[required].... +ENV_CONFIG_MAGICNUMBERS +..[description]. +..[type]........Comma-separated.list.of.Integer +..[default]..... +..[required].... +ENV_CONFIG_COLORCODES +..[description]. +..[type]........Comma-separated.list.of.String:Integer.pairs +..[default]..... +..[required].... +ENV_CONFIG_MULTIWORDVAR +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT +..[description]. +..[type]........Unsigned.Integer +..[default]..... +..[required].... +ENV_CONFIG_SOMEPOINTER +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_SOMEPOINTERWITHDEFAULT +..[description].foorbar.is.the.word +..[type]........String +..[default].....foo2baz +..[required].... +ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT +..[description].what.alt +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_SERVICE_HOST +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_DEFAULTVAR +..[description]. +..[type]........String +..[default].....foobar +..[required].... +ENV_CONFIG_REQUIREDVAR +..[description]. +..[type]........String +..[default]..... +..[required]....true +ENV_CONFIG_BROKER +..[description]. +..[type]........String +..[default].....127.0.0.1 +..[required].... +ENV_CONFIG_REQUIREDDEFAULT +..[description]. +..[type]........String +..[default].....foo2bar +..[required]....true +ENV_CONFIG_OUTER_INNER +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_OUTER_PROPERTYWITHDEFAULT +..[description]. +..[type]........String +..[default].....fuzzybydefault +..[required].... +ENV_CONFIG_AFTERNESTED +..[description]. +..[type]........String +..[default]..... +..[required].... +ENV_CONFIG_HONOR +..[description]. +..[type]........HonorDecodeInStruct +..[default]..... +..[required].... +ENV_CONFIG_DATETIME +..[description]. +..[type]........Time +..[default]..... +..[required].... diff --git a/vendor/github.com/kelseyhightower/envconfig/testdata/default_table.txt b/vendor/github.com/kelseyhightower/envconfig/testdata/default_table.txt new file mode 100644 index 000000000..f3cf945f7 --- /dev/null +++ b/vendor/github.com/kelseyhightower/envconfig/testdata/default_table.txt @@ -0,0 +1,34 @@ +This.application.is.configured.via.the.environment..The.following.environment +variables.can.be.used: + +KEY..............................................TYPE............................................DEFAULT...........REQUIRED....DESCRIPTION +ENV_CONFIG_ENABLED...............................True.or.False.................................................................some.embedded.value +ENV_CONFIG_EMBEDDEDPORT..........................Integer....................................................................... +ENV_CONFIG_MULTIWORDVAR..........................String........................................................................ +ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT..............String........................................................................ +ENV_CONFIG_EMBEDDED_WITH_ALT.....................String........................................................................ +ENV_CONFIG_DEBUG.................................True.or.False................................................................. +ENV_CONFIG_PORT..................................Integer....................................................................... +ENV_CONFIG_RATE..................................Float......................................................................... +ENV_CONFIG_USER..................................String........................................................................ +ENV_CONFIG_TTL...................................Unsigned.Integer.............................................................. +ENV_CONFIG_TIMEOUT...............................Duration...................................................................... +ENV_CONFIG_ADMINUSERS............................Comma-separated.list.of.String................................................ +ENV_CONFIG_MAGICNUMBERS..........................Comma-separated.list.of.Integer............................................... +ENV_CONFIG_COLORCODES............................Comma-separated.list.of.String:Integer.pairs.................................. +ENV_CONFIG_MULTIWORDVAR..........................String........................................................................ +ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT........Unsigned.Integer.............................................................. +ENV_CONFIG_SOMEPOINTER...........................String........................................................................ +ENV_CONFIG_SOMEPOINTERWITHDEFAULT................String..........................................foo2baz.......................foorbar.is.the.word +ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT...............String........................................................................what.alt +ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT....String........................................................................ +ENV_CONFIG_SERVICE_HOST..........................String........................................................................ +ENV_CONFIG_DEFAULTVAR............................String..........................................foobar........................ +ENV_CONFIG_REQUIREDVAR...........................String............................................................true........ +ENV_CONFIG_BROKER................................String..........................................127.0.0.1..................... +ENV_CONFIG_REQUIREDDEFAULT.......................String..........................................foo2bar...........true........ +ENV_CONFIG_OUTER_INNER...........................String........................................................................ +ENV_CONFIG_OUTER_PROPERTYWITHDEFAULT.............String..........................................fuzzybydefault................ +ENV_CONFIG_AFTERNESTED...........................String........................................................................ +ENV_CONFIG_HONOR.................................HonorDecodeInStruct........................................................... +ENV_CONFIG_DATETIME..............................Time.......................................................................... diff --git a/vendor/github.com/kelseyhightower/envconfig/testdata/fault.txt b/vendor/github.com/kelseyhightower/envconfig/testdata/fault.txt new file mode 100644 index 000000000..30e28ce08 --- /dev/null +++ b/vendor/github.com/kelseyhightower/envconfig/testdata/fault.txt @@ -0,0 +1,30 @@ +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} +{.Key} diff --git a/vendor/github.com/kelseyhightower/envconfig/usage.go b/vendor/github.com/kelseyhightower/envconfig/usage.go new file mode 100644 index 000000000..184635380 --- /dev/null +++ b/vendor/github.com/kelseyhightower/envconfig/usage.go @@ -0,0 +1,158 @@ +// Copyright (c) 2016 Kelsey Hightower and others. All rights reserved. +// Use of this source code is governed by the MIT License that can be found in +// the LICENSE file. + +package envconfig + +import ( + "encoding" + "fmt" + "io" + "os" + "reflect" + "strconv" + "strings" + "text/tabwriter" + "text/template" +) + +const ( + // DefaultListFormat constant to use to display usage in a list format + DefaultListFormat = `This application is configured via the environment. The following environment +variables can be used: +{{range .}} +{{usage_key .}} + [description] {{usage_description .}} + [type] {{usage_type .}} + [default] {{usage_default .}} + [required] {{usage_required .}}{{end}} +` + // DefaultTableFormat constant to use to display usage in a tabluar format + DefaultTableFormat = `This application is configured via the environment. The following environment +variables can be used: + +KEY TYPE DEFAULT REQUIRED DESCRIPTION +{{range .}}{{usage_key .}} {{usage_type .}} {{usage_default .}} {{usage_required .}} {{usage_description .}} +{{end}}` +) + +var ( + decoderType = reflect.TypeOf((*Decoder)(nil)).Elem() + setterType = reflect.TypeOf((*Setter)(nil)).Elem() + unmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() +) + +func implementsInterface(t reflect.Type) bool { + return t.Implements(decoderType) || + reflect.PtrTo(t).Implements(decoderType) || + t.Implements(setterType) || + reflect.PtrTo(t).Implements(setterType) || + t.Implements(unmarshalerType) || + reflect.PtrTo(t).Implements(unmarshalerType) +} + +// toTypeDescription converts Go types into a human readable description +func toTypeDescription(t reflect.Type) string { + switch t.Kind() { + case reflect.Array, reflect.Slice: + return fmt.Sprintf("Comma-separated list of %s", toTypeDescription(t.Elem())) + case reflect.Map: + return fmt.Sprintf( + "Comma-separated list of %s:%s pairs", + toTypeDescription(t.Key()), + toTypeDescription(t.Elem()), + ) + case reflect.Ptr: + return toTypeDescription(t.Elem()) + case reflect.Struct: + if implementsInterface(t) && t.Name() != "" { + return t.Name() + } + return "" + case reflect.String: + name := t.Name() + if name != "" && name != "string" { + return name + } + return "String" + case reflect.Bool: + name := t.Name() + if name != "" && name != "bool" { + return name + } + return "True or False" + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + name := t.Name() + if name != "" && !strings.HasPrefix(name, "int") { + return name + } + return "Integer" + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + name := t.Name() + if name != "" && !strings.HasPrefix(name, "uint") { + return name + } + return "Unsigned Integer" + case reflect.Float32, reflect.Float64: + name := t.Name() + if name != "" && !strings.HasPrefix(name, "float") { + return name + } + return "Float" + } + return fmt.Sprintf("%+v", t) +} + +// Usage writes usage information to stderr using the default header and table format +func Usage(prefix string, spec interface{}) error { + // The default is to output the usage information as a table + // Create tabwriter instance to support table output + tabs := tabwriter.NewWriter(os.Stdout, 1, 0, 4, ' ', 0) + + err := Usagef(prefix, spec, tabs, DefaultTableFormat) + tabs.Flush() + return err +} + +// Usagef writes usage information to the specified io.Writer using the specifed template specification +func Usagef(prefix string, spec interface{}, out io.Writer, format string) error { + + // Specify the default usage template functions + functions := template.FuncMap{ + "usage_key": func(v varInfo) string { return v.Key }, + "usage_description": func(v varInfo) string { return v.Tags.Get("desc") }, + "usage_type": func(v varInfo) string { return toTypeDescription(v.Field.Type()) }, + "usage_default": func(v varInfo) string { return v.Tags.Get("default") }, + "usage_required": func(v varInfo) (string, error) { + req := v.Tags.Get("required") + if req != "" { + reqB, err := strconv.ParseBool(req) + if err != nil { + return "", err + } + if reqB { + req = "true" + } + } + return req, nil + }, + } + + tmpl, err := template.New("envconfig").Funcs(functions).Parse(format) + if err != nil { + return err + } + + return Usaget(prefix, spec, out, tmpl) +} + +// Usaget writes usage information to the specified io.Writer using the specified template +func Usaget(prefix string, spec interface{}, out io.Writer, tmpl *template.Template) error { + // gather first + infos, err := gatherInfo(prefix, spec) + if err != nil { + return err + } + + return tmpl.Execute(out, infos) +} diff --git a/vendor/github.com/kelseyhightower/envconfig/usage_test.go b/vendor/github.com/kelseyhightower/envconfig/usage_test.go new file mode 100644 index 000000000..b433d1982 --- /dev/null +++ b/vendor/github.com/kelseyhightower/envconfig/usage_test.go @@ -0,0 +1,155 @@ +// Copyright (c) 2016 Kelsey Hightower and others. All rights reserved. +// Use of this source code is governed by the MIT License that can be found in +// the LICENSE file. + +package envconfig + +import ( + "bytes" + "io" + "io/ioutil" + "log" + "os" + "strings" + "testing" + "text/tabwriter" +) + +var testUsageTableResult, testUsageListResult, testUsageCustomResult, testUsageBadFormatResult string + +func TestMain(m *testing.M) { + + // Load the expected test results from a text file + data, err := ioutil.ReadFile("testdata/default_table.txt") + if err != nil { + log.Fatal(err) + } + testUsageTableResult = string(data) + + data, err = ioutil.ReadFile("testdata/default_list.txt") + if err != nil { + log.Fatal(err) + } + testUsageListResult = string(data) + + data, err = ioutil.ReadFile("testdata/custom.txt") + if err != nil { + log.Fatal(err) + } + testUsageCustomResult = string(data) + + data, err = ioutil.ReadFile("testdata/fault.txt") + if err != nil { + log.Fatal(err) + } + testUsageBadFormatResult = string(data) + + retCode := m.Run() + os.Exit(retCode) +} + +func compareUsage(want, got string, t *testing.T) { + got = strings.Replace(got, " ", ".", -1) + if want != got { + shortest := len(want) + if len(got) < shortest { + shortest = len(got) + } + if len(want) != len(got) { + t.Errorf("expected result length of %d, found %d", len(want), len(got)) + } + for i := 0; i < shortest; i++ { + if want[i] != got[i] { + t.Errorf("difference at index %d, expected '%c' (%v), found '%c' (%v)\n", + i, want[i], want[i], got[i], got[i]) + break + } + } + t.Errorf("Complete Expected:\n'%s'\nComplete Found:\n'%s'\n", want, got) + } +} + +func TestUsageDefault(t *testing.T) { + var s Specification + os.Clearenv() + save := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + err := Usage("env_config", &s) + outC := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + w.Close() + os.Stdout = save // restoring the real stdout + out := <-outC + + if err != nil { + t.Error(err.Error()) + } + compareUsage(testUsageTableResult, out, t) +} + +func TestUsageTable(t *testing.T) { + var s Specification + os.Clearenv() + buf := new(bytes.Buffer) + tabs := tabwriter.NewWriter(buf, 1, 0, 4, ' ', 0) + err := Usagef("env_config", &s, tabs, DefaultTableFormat) + tabs.Flush() + if err != nil { + t.Error(err.Error()) + } + compareUsage(testUsageTableResult, buf.String(), t) +} + +func TestUsageList(t *testing.T) { + var s Specification + os.Clearenv() + buf := new(bytes.Buffer) + err := Usagef("env_config", &s, buf, DefaultListFormat) + if err != nil { + t.Error(err.Error()) + } + compareUsage(testUsageListResult, buf.String(), t) +} + +func TestUsageCustomFormat(t *testing.T) { + var s Specification + os.Clearenv() + buf := new(bytes.Buffer) + err := Usagef("env_config", &s, buf, "{{range .}}{{usage_key .}}={{usage_description .}}\n{{end}}") + if err != nil { + t.Error(err.Error()) + } + compareUsage(testUsageCustomResult, buf.String(), t) +} + +func TestUsageUnknownKeyFormat(t *testing.T) { + var s Specification + unknownError := "template: envconfig:1:2: executing \"envconfig\" at <.UnknownKey>" + os.Clearenv() + buf := new(bytes.Buffer) + err := Usagef("env_config", &s, buf, "{{.UnknownKey}}") + if err == nil { + t.Errorf("expected 'unknown key' error, but got no error") + } + if strings.Index(err.Error(), unknownError) == -1 { + t.Errorf("expected '%s', but got '%s'", unknownError, err.Error()) + } +} + +func TestUsageBadFormat(t *testing.T) { + var s Specification + os.Clearenv() + // If you don't use two {{}} then you get a lieteral + buf := new(bytes.Buffer) + err := Usagef("env_config", &s, buf, "{{range .}}{.Key}\n{{end}}") + if err != nil { + t.Error(err.Error()) + } + compareUsage(testUsageBadFormatResult, buf.String(), t) +}