From 678a8cc192958092921749b0bf97ef8f2fe76ed6 Mon Sep 17 00:00:00 2001 From: Sergey Bogatyrets Date: Wed, 13 Jun 2018 22:34:35 +0300 Subject: [PATCH] Use env lib, add PROXY_PORT ENV variable --- Gopkg.lock | 8 +- Gopkg.toml | 4 + server.go | 29 +- vendor/github.com/caarlos0/env/.gitignore | 1 + vendor/github.com/caarlos0/env/.hound.yml | 2 + vendor/github.com/caarlos0/env/.travis.yml | 16 + vendor/github.com/caarlos0/env/LICENSE.md | 21 + vendor/github.com/caarlos0/env/README.md | 115 ++++ vendor/github.com/caarlos0/env/env.go | 398 +++++++++++++ vendor/github.com/caarlos0/env/env_test.go | 550 ++++++++++++++++++ .../github.com/caarlos0/env/examples/first.go | 48 ++ .../github.com/caarlos0/env/parsers/README.md | 35 ++ .../caarlos0/env/parsers/parsers.go | 23 + 13 files changed, 1241 insertions(+), 9 deletions(-) create mode 100644 vendor/github.com/caarlos0/env/.gitignore create mode 100644 vendor/github.com/caarlos0/env/.hound.yml create mode 100644 vendor/github.com/caarlos0/env/.travis.yml create mode 100644 vendor/github.com/caarlos0/env/LICENSE.md create mode 100644 vendor/github.com/caarlos0/env/README.md create mode 100644 vendor/github.com/caarlos0/env/env.go create mode 100644 vendor/github.com/caarlos0/env/env_test.go create mode 100644 vendor/github.com/caarlos0/env/examples/first.go create mode 100644 vendor/github.com/caarlos0/env/parsers/README.md create mode 100644 vendor/github.com/caarlos0/env/parsers/parsers.go diff --git a/Gopkg.lock b/Gopkg.lock index 74409e1..c8a59eb 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -7,6 +7,12 @@ packages = ["."] revision = "e75332964ef517daa070d7c38a9466a0d687e0a5" +[[projects]] + branch = "master" + name = "github.com/caarlos0/env" + packages = ["."] + revision = "3e0f30cbf50bb82273cf60e7bb70869f4852e9b8" + [[projects]] branch = "master" name = "golang.org/x/net" @@ -16,6 +22,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "bb1b87ff840d0a559073f327d2c493d6ca2c37ed971b13eef6796925f3980a7a" + inputs-digest = "b579eb59bf4c9ca68a34a1b121d49796b87e80c2514576b22c53c4507f129c87" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 817d76b..45b7bb3 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -24,3 +24,7 @@ [[constraint]] branch = "master" name = "github.com/armon/go-socks5" + +[[constraint]] + branch = "master" + name = "github.com/caarlos0/env" \ No newline at end of file diff --git a/server.go b/server.go index ced2dff..21adf81 100644 --- a/server.go +++ b/server.go @@ -1,34 +1,47 @@ package main import ( - "fmt" "log" "os" "github.com/armon/go-socks5" + "github.com/caarlos0/env" ) -func main() { +type params struct { + User string `env:"PROXY_USER" envDefault:""` + Password string `env:"PROXY_PASSWORD" envDefault:""` + Port string `env:"PROXY_PORT" envDefault:"1080"` +} - conf := &socks5.Config{ +func main() { + // Working with app params + cfg := params{} + err := env.Parse(&cfg) + if err != nil { + log.Printf("%+v\n", err) + } + + //Initialize socks5 config + socsk5conf := &socks5.Config{ Logger: log.New(os.Stdout, "", log.LstdFlags), } - if os.Getenv("PROXY_USER")+os.Getenv("PROXY_PASSWORD") != "" { + if cfg.User+cfg.Password != "" { creds := socks5.StaticCredentials{ os.Getenv("PROXY_USER"): os.Getenv("PROXY_PASSWORD"), } cator := socks5.UserPassAuthenticator{Credentials: creds} - conf.AuthMethods = []socks5.Authenticator{cator} + socsk5conf.AuthMethods = []socks5.Authenticator{cator} } - server, err := socks5.New(conf) + server, err := socks5.New(socsk5conf) if err != nil { log.Fatal(err) } - fmt.Println("Start listening ...") - if err := server.ListenAndServe("tcp", ":1080"); err != nil { + log.Printf("Start listening proxy service on port %s\n", cfg.Port) + if err := server.ListenAndServe("tcp", ":"+cfg.Port); err != nil { log.Fatal(err) } } diff --git a/vendor/github.com/caarlos0/env/.gitignore b/vendor/github.com/caarlos0/env/.gitignore new file mode 100644 index 0000000..2d83068 --- /dev/null +++ b/vendor/github.com/caarlos0/env/.gitignore @@ -0,0 +1 @@ +coverage.out diff --git a/vendor/github.com/caarlos0/env/.hound.yml b/vendor/github.com/caarlos0/env/.hound.yml new file mode 100644 index 0000000..e5c719d --- /dev/null +++ b/vendor/github.com/caarlos0/env/.hound.yml @@ -0,0 +1,2 @@ +go: + enabled: true diff --git a/vendor/github.com/caarlos0/env/.travis.yml b/vendor/github.com/caarlos0/env/.travis.yml new file mode 100644 index 0000000..4ad7e69 --- /dev/null +++ b/vendor/github.com/caarlos0/env/.travis.yml @@ -0,0 +1,16 @@ +language: go +go: + - 1.5 + - 1.6 + - 1.7 + - 1.8 + - tip +before_install: + - go get github.com/axw/gocov/gocov + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - go test -v -cover -race -coverprofile=coverage.out +after_script: + - go get github.com/mattn/goveralls + - goveralls -coverprofile=coverage.out -service=travis-ci -repotoken='eCcizKmTdSaJCz8Ih33WDppdqb9kioYwi' diff --git a/vendor/github.com/caarlos0/env/LICENSE.md b/vendor/github.com/caarlos0/env/LICENSE.md new file mode 100644 index 0000000..b739867 --- /dev/null +++ b/vendor/github.com/caarlos0/env/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2016 Carlos Alexandro Becker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/caarlos0/env/README.md b/vendor/github.com/caarlos0/env/README.md new file mode 100644 index 0000000..cf46c54 --- /dev/null +++ b/vendor/github.com/caarlos0/env/README.md @@ -0,0 +1,115 @@ +# env [![Build Status](https://travis-ci.org/caarlos0/env.svg?branch=master)](https://travis-ci.org/caarlos0/env) [![Coverage Status](https://coveralls.io/repos/caarlos0/env/badge.svg?branch=master&service=github)](https://coveralls.io/github/caarlos0/env?branch=master) [![](https://godoc.org/github.com/caarlos0/env?status.svg)](http://godoc.org/github.com/caarlos0/env) [![](http://goreportcard.com/badge/caarlos0/env)](http://goreportcard.com/report/caarlos0/env) [![SayThanks.io](https://img.shields.io/badge/SayThanks.io-%E2%98%BC-1EAEDB.svg?style=flat-square)](https://saythanks.io/to/caarlos0) + +A KISS way to deal with environment variables in Go. + +## Why + +At first, it was boring for me to write down an entire function just to +get some `var` from the environment and default to another in case it's missing. + +For that manner, I wrote a `GetOr` function in the +[go-idioms](https://github.com/caarlos0/go-idioms) project. + +Then, I got pissed about writing `os.Getenv`, `os.Setenv`, `os.Unsetenv`... +it kind of make more sense to me write it as `env.Get`, `env.Set`, `env.Unset`. +So I did. + +Then I got a better idea: to use `struct` tags to do all that work for me. + +## Example + +A very basic example (check the `examples` folder): + +```go +package main + +import ( + "fmt" + "time" + + "github.com/caarlos0/env" +) + +type config struct { + Home string `env:"HOME"` + Port int `env:"PORT" envDefault:"3000"` + IsProduction bool `env:"PRODUCTION"` + Hosts []string `env:"HOSTS" envSeparator:":"` + Duration time.Duration `env:"DURATION"` +} + +func main() { + cfg := config{} + err := env.Parse(&cfg) + if err != nil { + fmt.Printf("%+v\n", err) + } + fmt.Printf("%+v\n", cfg) +} +``` + +You can run it like this: + +```sh +$ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run examples/first.go +{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s} +``` + +## Supported types and defaults + +The library has built-in support for the following types: + +* `string` +* `int` +* `uint` +* `int64` +* `bool` +* `float32` +* `float64` +* `time.Duration` +* `[]string` +* `[]int` +* `[]bool` +* `[]float32` +* `[]float64` +* `[]time.Duration` +* .. or use/define a [custom parser func](#custom-parser-funcs) for any other type + +If you set the `envDefault` tag for something, this value will be used in the +case of absence of it in the environment. If you don't do that AND the +environment variable is also not set, the zero-value +of the type will be used: empty for `string`s, `false` for `bool`s +and `0` for `int`s. + +By default, slice types will split the environment value on `,`; you can change this behavior by setting the `envSeparator` tag. + +## Custom Parser Funcs + +If you have a type that is not supported out of the box by the lib, you are able +to use (or define) and pass custom parsers (and their associated `reflect.Type`) to the +`env.ParseWithFuncs()` function. + +In addition to accepting a struct pointer (same as `Parse()`), this function also +accepts a `env.CustomParsers` arg that under the covers is a `map[reflect.Type]env.ParserFunc`. + +To see what this looks like in practice, take a look at the [commented block in the example](https://github.com/caarlos0/env/blob/master/examples/first.go#L35-L39). + +`env` also ships with some pre-built custom parser funcs for common types. You +can check them out [here](parsers/). + +## Required fields + +The `env` tag option `required` (e.g., `env:"tagKey,required"`) can be added +to ensure that some environment variable is set. In the example above, +an error is returned if the `config` struct is changed to: + + +```go +type config struct { + Home string `env:"HOME"` + Port int `env:"PORT" envDefault:"3000"` + IsProduction bool `env:"PRODUCTION"` + Hosts []string `env:"HOSTS" envSeparator:":"` + SecretKey string `env:"SECRET_KEY,required"` +} +``` diff --git a/vendor/github.com/caarlos0/env/env.go b/vendor/github.com/caarlos0/env/env.go new file mode 100644 index 0000000..e06fbd0 --- /dev/null +++ b/vendor/github.com/caarlos0/env/env.go @@ -0,0 +1,398 @@ +package env + +import ( + "errors" + "fmt" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +var ( + // ErrNotAStructPtr is returned if you pass something that is not a pointer to a + // Struct to Parse + ErrNotAStructPtr = errors.New("Expected a pointer to a Struct") + // ErrUnsupportedType if the struct field type is not supported by env + ErrUnsupportedType = errors.New("Type is not supported") + // ErrUnsupportedSliceType if the slice element type is not supported by env + ErrUnsupportedSliceType = errors.New("Unsupported slice type") + // OnEnvVarSet is an optional convenience callback, such as for logging purposes. + // If not nil, it's called after successfully setting the given field from the given value. + OnEnvVarSet func(reflect.StructField, string) + // Friendly names for reflect types + sliceOfInts = reflect.TypeOf([]int(nil)) + sliceOfInt64s = reflect.TypeOf([]int64(nil)) + sliceOfUint64s = reflect.TypeOf([]uint64(nil)) + sliceOfStrings = reflect.TypeOf([]string(nil)) + sliceOfBools = reflect.TypeOf([]bool(nil)) + sliceOfFloat32s = reflect.TypeOf([]float32(nil)) + sliceOfFloat64s = reflect.TypeOf([]float64(nil)) + sliceOfDurations = reflect.TypeOf([]time.Duration(nil)) +) + +// CustomParsers is a friendly name for the type that `ParseWithFuncs()` accepts +type CustomParsers map[reflect.Type]ParserFunc + +// ParserFunc defines the signature of a function that can be used within `CustomParsers` +type ParserFunc func(v string) (interface{}, error) + +// Parse parses a struct containing `env` tags and loads its values from +// environment variables. +func Parse(v interface{}) error { + ptrRef := reflect.ValueOf(v) + if ptrRef.Kind() != reflect.Ptr { + return ErrNotAStructPtr + } + ref := ptrRef.Elem() + if ref.Kind() != reflect.Struct { + return ErrNotAStructPtr + } + return doParse(ref, make(map[reflect.Type]ParserFunc, 0)) +} + +// ParseWithFuncs is the same as `Parse` except it also allows the user to pass +// in custom parsers. +func ParseWithFuncs(v interface{}, funcMap CustomParsers) error { + ptrRef := reflect.ValueOf(v) + if ptrRef.Kind() != reflect.Ptr { + return ErrNotAStructPtr + } + ref := ptrRef.Elem() + if ref.Kind() != reflect.Struct { + return ErrNotAStructPtr + } + return doParse(ref, funcMap) +} + +func doParse(ref reflect.Value, funcMap CustomParsers) error { + refType := ref.Type() + var errorList []string + + for i := 0; i < refType.NumField(); i++ { + refField := ref.Field(i) + if reflect.Ptr == refField.Kind() && !refField.IsNil() && refField.CanSet() { + err := Parse(refField.Interface()) + if nil != err { + return err + } + continue + } + refTypeField := refType.Field(i) + value, err := get(refTypeField) + if err != nil { + errorList = append(errorList, err.Error()) + continue + } + if value == "" { + continue + } + if err := set(refField, refTypeField, value, funcMap); err != nil { + errorList = append(errorList, err.Error()) + continue + } + if OnEnvVarSet != nil { + OnEnvVarSet(refTypeField, value) + } + } + if len(errorList) == 0 { + return nil + } + return errors.New(strings.Join(errorList, ". ")) +} + +func get(field reflect.StructField) (string, error) { + var ( + val string + err error + ) + + key, opts := parseKeyForOption(field.Tag.Get("env")) + + defaultValue := field.Tag.Get("envDefault") + val = getOr(key, defaultValue) + + if len(opts) > 0 { + for _, opt := range opts { + // The only option supported is "required". + switch opt { + case "": + break + case "required": + val, err = getRequired(key) + default: + err = errors.New("Env tag option " + opt + " not supported.") + } + } + } + + return val, err +} + +// split the env tag's key into the expected key and desired option, if any. +func parseKeyForOption(key string) (string, []string) { + opts := strings.Split(key, ",") + return opts[0], opts[1:] +} + +func getRequired(key string) (string, error) { + if value, ok := os.LookupEnv(key); ok { + return value, nil + } + // We do not use fmt.Errorf to avoid another import. + return "", errors.New("Required environment variable " + key + " is not set") +} + +func getOr(key, defaultValue string) string { + value, ok := os.LookupEnv(key) + if ok { + return value + } + return defaultValue +} + +func set(field reflect.Value, refType reflect.StructField, value string, funcMap CustomParsers) error { + switch field.Kind() { + case reflect.Slice: + separator := refType.Tag.Get("envSeparator") + return handleSlice(field, value, separator) + case reflect.String: + field.SetString(value) + case reflect.Bool: + bvalue, err := strconv.ParseBool(value) + if err != nil { + return err + } + field.SetBool(bvalue) + case reflect.Int: + intValue, err := strconv.ParseInt(value, 10, 32) + if err != nil { + return err + } + field.SetInt(intValue) + case reflect.Uint: + uintValue, err := strconv.ParseUint(value, 10, 32) + if err != nil { + return err + } + field.SetUint(uintValue) + case reflect.Float32: + v, err := strconv.ParseFloat(value, 32) + if err != nil { + return err + } + field.SetFloat(v) + case reflect.Float64: + v, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + field.Set(reflect.ValueOf(v)) + case reflect.Int64: + if refType.Type.String() == "time.Duration" { + dValue, err := time.ParseDuration(value) + if err != nil { + return err + } + field.Set(reflect.ValueOf(dValue)) + } else { + intValue, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + field.SetInt(intValue) + } + case reflect.Uint64: + uintValue, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + field.SetUint(uintValue) + case reflect.Struct: + return handleStruct(field, refType, value, funcMap) + default: + parserFunc, ok := funcMap[refType.Type] + if !ok { + return ErrUnsupportedType + } + val, err := parserFunc(value) + if err != nil { + return err + } + field.Set(reflect.ValueOf(val)) + } + return nil +} + +func handleStruct(field reflect.Value, refType reflect.StructField, value string, funcMap CustomParsers) error { + // Does the custom parser func map contain this type? + parserFunc, ok := funcMap[field.Type()] + if !ok { + // Map does not contain a custom parser for this type + return ErrUnsupportedType + } + + // Call on the custom parser func + data, err := parserFunc(value) + if err != nil { + return fmt.Errorf("Custom parser error: %v", err) + } + + // Set the field to the data returned by the customer parser func + rv := reflect.ValueOf(data) + field.Set(rv) + + return nil +} + +func handleSlice(field reflect.Value, value, separator string) error { + if separator == "" { + separator = "," + } + + splitData := strings.Split(value, separator) + + switch field.Type() { + case sliceOfStrings: + field.Set(reflect.ValueOf(splitData)) + case sliceOfInts: + intData, err := parseInts(splitData) + if err != nil { + return err + } + field.Set(reflect.ValueOf(intData)) + case sliceOfInt64s: + int64Data, err := parseInt64s(splitData) + if err != nil { + return err + } + field.Set(reflect.ValueOf(int64Data)) + case sliceOfUint64s: + uint64Data, err := parseUint64s(splitData) + if err != nil { + return err + } + field.Set(reflect.ValueOf(uint64Data)) + case sliceOfFloat32s: + data, err := parseFloat32s(splitData) + if err != nil { + return err + } + field.Set(reflect.ValueOf(data)) + case sliceOfFloat64s: + data, err := parseFloat64s(splitData) + if err != nil { + return err + } + field.Set(reflect.ValueOf(data)) + case sliceOfBools: + boolData, err := parseBools(splitData) + if err != nil { + return err + } + field.Set(reflect.ValueOf(boolData)) + case sliceOfDurations: + durationData, err := parseDurations(splitData) + if err != nil { + return err + } + field.Set(reflect.ValueOf(durationData)) + default: + return ErrUnsupportedSliceType + } + return nil +} + +func parseInts(data []string) ([]int, error) { + intSlice := make([]int, 0, len(data)) + + for _, v := range data { + intValue, err := strconv.ParseInt(v, 10, 32) + if err != nil { + return nil, err + } + intSlice = append(intSlice, int(intValue)) + } + return intSlice, nil +} + +func parseInt64s(data []string) ([]int64, error) { + intSlice := make([]int64, 0, len(data)) + + for _, v := range data { + intValue, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return nil, err + } + intSlice = append(intSlice, int64(intValue)) + } + return intSlice, nil +} + +func parseUint64s(data []string) ([]uint64, error) { + var uintSlice []uint64 + + for _, v := range data { + uintValue, err := strconv.ParseUint(v, 10, 64) + if err != nil { + return nil, err + } + uintSlice = append(uintSlice, uint64(uintValue)) + } + return uintSlice, nil +} + +func parseFloat32s(data []string) ([]float32, error) { + float32Slice := make([]float32, 0, len(data)) + + for _, v := range data { + data, err := strconv.ParseFloat(v, 32) + if err != nil { + return nil, err + } + float32Slice = append(float32Slice, float32(data)) + } + return float32Slice, nil +} + +func parseFloat64s(data []string) ([]float64, error) { + float64Slice := make([]float64, 0, len(data)) + + for _, v := range data { + data, err := strconv.ParseFloat(v, 64) + if err != nil { + return nil, err + } + float64Slice = append(float64Slice, float64(data)) + } + return float64Slice, nil +} + +func parseBools(data []string) ([]bool, error) { + boolSlice := make([]bool, 0, len(data)) + + for _, v := range data { + bvalue, err := strconv.ParseBool(v) + if err != nil { + return nil, err + } + + boolSlice = append(boolSlice, bvalue) + } + return boolSlice, nil +} + +func parseDurations(data []string) ([]time.Duration, error) { + durationSlice := make([]time.Duration, 0, len(data)) + + for _, v := range data { + dvalue, err := time.ParseDuration(v) + if err != nil { + return nil, err + } + + durationSlice = append(durationSlice, dvalue) + } + return durationSlice, nil +} diff --git a/vendor/github.com/caarlos0/env/env_test.go b/vendor/github.com/caarlos0/env/env_test.go new file mode 100644 index 0000000..86243d1 --- /dev/null +++ b/vendor/github.com/caarlos0/env/env_test.go @@ -0,0 +1,550 @@ +package env_test + +import ( + "errors" + "fmt" + "net/http" + "os" + "reflect" + "strconv" + "testing" + "time" + + "github.com/caarlos0/env" + "github.com/stretchr/testify/assert" +) + +type Config struct { + Some string `env:"somevar"` + Other bool `env:"othervar"` + Port int `env:"PORT"` + Int64Val int64 `env:"INT64VAL"` + UintVal uint `env:"UINTVAL"` + Uint64Val uint64 `env:"UINT64VAL"` + NotAnEnv string + DatabaseURL string `env:"DATABASE_URL" envDefault:"postgres://localhost:5432/db"` + Strings []string `env:"STRINGS"` + SepStrings []string `env:"SEPSTRINGS" envSeparator:":"` + Numbers []int `env:"NUMBERS"` + Numbers64 []int64 `env:"NUMBERS64"` + UNumbers64 []uint64 `env:"UNUMBERS64"` + Bools []bool `env:"BOOLS"` + Duration time.Duration `env:"DURATION"` + Float32 float32 `env:"FLOAT32"` + Float64 float64 `env:"FLOAT64"` + Float32s []float32 `env:"FLOAT32S"` + Float64s []float64 `env:"FLOAT64S"` + Durations []time.Duration `env:"DURATIONS"` +} + +type ParentStruct struct { + InnerStruct *InnerStruct + unexported *InnerStruct + Ignored *http.Client +} + +type InnerStruct struct { + Inner string `env:"innervar"` + Number uint `env:"innernum"` +} + +func TestParsesEnv(t *testing.T) { + os.Setenv("somevar", "somevalue") + os.Setenv("othervar", "true") + os.Setenv("PORT", "8080") + os.Setenv("STRINGS", "string1,string2,string3") + os.Setenv("SEPSTRINGS", "string1:string2:string3") + os.Setenv("NUMBERS", "1,2,3,4") + os.Setenv("NUMBERS64", "1,2,2147483640,-2147483640") + os.Setenv("UNUMBERS64", "1,2,214748364011,9147483641") + os.Setenv("BOOLS", "t,TRUE,0,1") + os.Setenv("DURATION", "1s") + os.Setenv("FLOAT32", "3.40282346638528859811704183484516925440e+38") + os.Setenv("FLOAT64", "1.797693134862315708145274237317043567981e+308") + os.Setenv("FLOAT32S", "1.0,2.0,3.0") + os.Setenv("FLOAT64S", "1.0,2.0,3.0") + os.Setenv("UINTVAL", "44") + os.Setenv("UINT64VAL", "6464") + os.Setenv("INT64VAL", "-7575") + os.Setenv("DURATIONS", "1s,2s,3s") + + defer os.Clearenv() + + cfg := Config{} + assert.NoError(t, env.Parse(&cfg)) + assert.Equal(t, "somevalue", cfg.Some) + assert.Equal(t, true, cfg.Other) + assert.Equal(t, 8080, cfg.Port) + assert.Equal(t, uint(44), cfg.UintVal) + assert.Equal(t, int64(-7575), cfg.Int64Val) + assert.Equal(t, uint64(6464), cfg.Uint64Val) + assert.Equal(t, []string{"string1", "string2", "string3"}, cfg.Strings) + assert.Equal(t, []string{"string1", "string2", "string3"}, cfg.SepStrings) + assert.Equal(t, []int{1, 2, 3, 4}, cfg.Numbers) + assert.Equal(t, []int64{1, 2, 2147483640, -2147483640}, cfg.Numbers64) + assert.Equal(t, []uint64{1, 2, 214748364011, 9147483641}, cfg.UNumbers64) + assert.Equal(t, []bool{true, true, false, true}, cfg.Bools) + d1, _ := time.ParseDuration("1s") + assert.Equal(t, d1, cfg.Duration) + f32 := float32(3.40282346638528859811704183484516925440e+38) + assert.Equal(t, f32, cfg.Float32) + f64 := float64(1.797693134862315708145274237317043567981e+308) + assert.Equal(t, f64, cfg.Float64) + assert.Equal(t, []float32{float32(1.0), float32(2.0), float32(3.0)}, cfg.Float32s) + assert.Equal(t, []float64{float64(1.0), float64(2.0), float64(3.0)}, cfg.Float64s) + d2, _ := time.ParseDuration("2s") + d3, _ := time.ParseDuration("3s") + assert.Equal(t, []time.Duration{d1, d2, d3}, cfg.Durations) +} + +func TestParsesEnvInner(t *testing.T) { + os.Setenv("innervar", "someinnervalue") + defer os.Clearenv() + cfg := ParentStruct{ + InnerStruct: &InnerStruct{}, + unexported: &InnerStruct{}, + } + assert.NoError(t, env.Parse(&cfg)) + assert.Equal(t, "someinnervalue", cfg.InnerStruct.Inner) +} + +func TestParsesEnvInnerNil(t *testing.T) { + os.Setenv("innervar", "someinnervalue") + defer os.Clearenv() + cfg := ParentStruct{} + assert.NoError(t, env.Parse(&cfg)) +} + +func TestParsesEnvInnerInvalid(t *testing.T) { + os.Setenv("innernum", "-547") + defer os.Clearenv() + cfg := ParentStruct{ + InnerStruct: &InnerStruct{}, + } + assert.Error(t, env.Parse(&cfg)) +} + +func TestEmptyVars(t *testing.T) { + cfg := Config{} + assert.NoError(t, env.Parse(&cfg)) + assert.Equal(t, "", cfg.Some) + assert.Equal(t, false, cfg.Other) + assert.Equal(t, 0, cfg.Port) + assert.Equal(t, uint(0), cfg.UintVal) + assert.Equal(t, uint64(0), cfg.Uint64Val) + assert.Equal(t, int64(0), cfg.Int64Val) + assert.Equal(t, 0, len(cfg.Strings)) + assert.Equal(t, 0, len(cfg.SepStrings)) + assert.Equal(t, 0, len(cfg.Numbers)) + assert.Equal(t, 0, len(cfg.Bools)) +} + +func TestPassAnInvalidPtr(t *testing.T) { + var thisShouldBreak int + assert.Error(t, env.Parse(&thisShouldBreak)) +} + +func TestPassReference(t *testing.T) { + cfg := Config{} + assert.Error(t, env.Parse(cfg)) +} + +func TestInvalidBool(t *testing.T) { + os.Setenv("othervar", "should-be-a-bool") + defer os.Clearenv() + + cfg := Config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestInvalidInt(t *testing.T) { + os.Setenv("PORT", "should-be-an-int") + defer os.Clearenv() + + cfg := Config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestInvalidUint(t *testing.T) { + os.Setenv("UINTVAL", "-44") + defer os.Clearenv() + + cfg := Config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestInvalidFloat32(t *testing.T) { + os.Setenv("FLOAT32", "AAA") + defer os.Clearenv() + + cfg := Config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestInvalidFloat64(t *testing.T) { + os.Setenv("FLOAT64", "AAA") + defer os.Clearenv() + + cfg := Config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestInvalidUint64(t *testing.T) { + os.Setenv("UINT64VAL", "AAA") + defer os.Clearenv() + + cfg := Config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestInvalidInt64(t *testing.T) { + os.Setenv("INT64VAL", "AAA") + defer os.Clearenv() + + cfg := Config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestInvalidInt64Slice(t *testing.T) { + type config struct { + BadFloats []int64 `env:"BADINTS"` + } + + os.Setenv("BADINTS", "A,2,3") + cfg := &config{} + assert.Error(t, env.Parse(cfg)) +} + +func TestInvalidUInt64Slice(t *testing.T) { + type config struct { + BadFloats []uint64 `env:"BADINTS"` + } + + os.Setenv("BADFLOATS", "A,2,3") + cfg := &config{} + assert.Error(t, env.Parse(cfg)) +} + +func TestInvalidFloat32Slice(t *testing.T) { + type config struct { + BadFloats []float32 `env:"BADFLOATS"` + } + + os.Setenv("BADFLOATS", "A,2.0,3.0") + cfg := &config{} + assert.Error(t, env.Parse(cfg)) +} + +func TestInvalidFloat64Slice(t *testing.T) { + type config struct { + BadFloats []float64 `env:"BADFLOATS"` + } + + os.Setenv("BADFLOATS", "A,2.0,3.0") + cfg := &config{} + assert.Error(t, env.Parse(cfg)) +} + +func TestInvalidBoolsSlice(t *testing.T) { + type config struct { + BadBools []bool `env:"BADBOOLS"` + } + + os.Setenv("BADBOOLS", "t,f,TRUE,faaaalse") + cfg := &config{} + assert.Error(t, env.Parse(cfg)) +} + +func TestInvalidDuration(t *testing.T) { + os.Setenv("DURATION", "should-be-a-valid-duration") + defer os.Clearenv() + + cfg := Config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestInvalidDurations(t *testing.T) { + os.Setenv("DURATIONS", "1s,contains-an-invalid-duration,3s") + defer os.Clearenv() + + cfg := Config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestParsesDefaultConfig(t *testing.T) { + cfg := Config{} + assert.NoError(t, env.Parse(&cfg)) + assert.Equal(t, "postgres://localhost:5432/db", cfg.DatabaseURL) +} + +func TestParseStructWithoutEnvTag(t *testing.T) { + cfg := Config{} + assert.NoError(t, env.Parse(&cfg)) + assert.Empty(t, cfg.NotAnEnv) +} + +func TestParseStructWithInvalidFieldKind(t *testing.T) { + type config struct { + WontWorkByte byte `env:"BLAH"` + } + os.Setenv("BLAH", "a") + cfg := config{} + assert.Error(t, env.Parse(&cfg)) +} + +func TestUnsupportedSliceType(t *testing.T) { + type config struct { + WontWork []map[int]int `env:"WONTWORK"` + } + + os.Setenv("WONTWORK", "1,2,3") + defer os.Clearenv() + + cfg := &config{} + assert.Error(t, env.Parse(cfg)) +} + +func TestBadSeparator(t *testing.T) { + type config struct { + WontWork []int `env:"WONTWORK" envSeparator:":"` + } + + cfg := &config{} + os.Setenv("WONTWORK", "1,2,3,4") + defer os.Clearenv() + + assert.Error(t, env.Parse(cfg)) +} + +func TestNoErrorRequiredSet(t *testing.T) { + type config struct { + IsRequired string `env:"IS_REQUIRED,required"` + } + + cfg := &config{} + + os.Setenv("IS_REQUIRED", "val") + defer os.Clearenv() + assert.NoError(t, env.Parse(cfg)) + assert.Equal(t, "val", cfg.IsRequired) +} + +func TestErrorRequiredNotSet(t *testing.T) { + type config struct { + IsRequired string `env:"IS_REQUIRED,required"` + } + + cfg := &config{} + assert.Error(t, env.Parse(cfg)) +} + +func TestCustomParser(t *testing.T) { + type foo struct { + name string + } + + type config struct { + Var foo `env:"VAR"` + } + + os.Setenv("VAR", "test") + + customParserFunc := func(v string) (interface{}, error) { + return foo{name: v}, nil + } + + cfg := &config{} + err := env.ParseWithFuncs(cfg, map[reflect.Type]env.ParserFunc{ + reflect.TypeOf(foo{}): customParserFunc, + }) + + assert.NoError(t, err) + assert.Equal(t, cfg.Var.name, "test") +} + +func TestParseWithFuncsNoPtr(t *testing.T) { + type foo struct{} + err := env.ParseWithFuncs(foo{}, nil) + assert.Error(t, err) + assert.Equal(t, err, env.ErrNotAStructPtr) +} + +func TestParseWithFuncsInvalidType(t *testing.T) { + var c int + err := env.ParseWithFuncs(&c, nil) + assert.Error(t, err) + assert.Equal(t, err, env.ErrNotAStructPtr) +} + +func TestCustomParserError(t *testing.T) { + type foo struct { + name string + } + + type config struct { + Var foo `env:"VAR"` + } + + os.Setenv("VAR", "test") + + customParserFunc := func(v string) (interface{}, error) { + return nil, errors.New("something broke") + } + + cfg := &config{} + err := env.ParseWithFuncs(cfg, map[reflect.Type]env.ParserFunc{ + reflect.TypeOf(foo{}): customParserFunc, + }) + + assert.Empty(t, cfg.Var.name, "Var.name should not be filled out when parse errors") + assert.Error(t, err) + assert.Equal(t, err.Error(), "Custom parser error: something broke") +} + +func TestCustomParserBasicType(t *testing.T) { + type ConstT int32 + + type config struct { + Const ConstT `env:"CONST_VAL"` + } + + exp := ConstT(123) + os.Setenv("CONST_VAL", fmt.Sprintf("%d", exp)) + + customParserFunc := func(v string) (interface{}, error) { + i, err := strconv.Atoi(v) + if err != nil { + return nil, err + } + r := ConstT(i) + return r, nil + } + + cfg := &config{} + err := env.ParseWithFuncs(cfg, map[reflect.Type]env.ParserFunc{ + reflect.TypeOf(ConstT(0)): customParserFunc, + }) + + assert.NoError(t, err) + assert.Equal(t, exp, cfg.Const) +} + +func TypeCustomParserBasicInvalid(t *testing.T) { + type ConstT int32 + + type config struct { + Const ConstT `env:"CONST_VAL"` + } + + os.Setenv("CONST_VAL", "foobar") + + expErr := errors.New("Random error") + customParserFunc := func(_ string) (interface{}, error) { + return nil, expErr + } + + cfg := &config{} + err := env.ParseWithFuncs(cfg, map[reflect.Type]env.ParserFunc{ + reflect.TypeOf(ConstT(0)): customParserFunc, + }) + + assert.Empty(t, cfg.Const) + assert.Error(t, err) + assert.Equal(t, expErr, err) +} + +func TestCustomParserBasicUnsupported(t *testing.T) { + type ConstT int32 + + type config struct { + Const ConstT `env:"CONST_VAL"` + } + + exp := ConstT(123) + os.Setenv("CONST_VAL", fmt.Sprintf("%d", exp)) + + cfg := &config{} + err := env.Parse(cfg) + + assert.Zero(t, cfg.Const) + assert.Error(t, err) + assert.Equal(t, env.ErrUnsupportedType, err) +} + +func TestUnsupportedStructType(t *testing.T) { + type config struct { + Foo http.Client `env:"FOO"` + } + + os.Setenv("FOO", "foo") + + cfg := &config{} + err := env.Parse(cfg) + + assert.Error(t, err) + assert.Equal(t, env.ErrUnsupportedType, err) +} + +func TestEmptyOption(t *testing.T) { + type config struct { + Var string `env:"VAR,"` + } + + cfg := &config{} + + os.Setenv("VAR", "val") + defer os.Clearenv() + assert.NoError(t, env.Parse(cfg)) + assert.Equal(t, "val", cfg.Var) +} + +func TestErrorOptionNotRecognized(t *testing.T) { + type config struct { + Var string `env:"VAR,not_supported!"` + } + + cfg := &config{} + assert.Error(t, env.Parse(cfg)) + +} + +func ExampleParse() { + type config struct { + Home string `env:"HOME"` + Port int `env:"PORT" envDefault:"3000"` + IsProduction bool `env:"PRODUCTION"` + } + os.Setenv("HOME", "/tmp/fakehome") + cfg := config{} + env.Parse(&cfg) + fmt.Println(cfg) + // Output: {/tmp/fakehome 3000 false} +} + +func ExampleParseRequiredField() { + type config struct { + Home string `env:"HOME"` + Port int `env:"PORT" envDefault:"3000"` + IsProduction bool `env:"PRODUCTION"` + SecretKey string `env:"SECRET_KEY,required"` + } + os.Setenv("HOME", "/tmp/fakehome") + cfg := config{} + err := env.Parse(&cfg) + fmt.Println(err) + // Output: Required environment variable SECRET_KEY is not set +} + +func ExampleParseMultipleOptions() { + type config struct { + Home string `env:"HOME"` + Port int `env:"PORT" envDefault:"3000"` + IsProduction bool `env:"PRODUCTION"` + SecretKey string `env:"SECRET_KEY,required,option1"` + } + os.Setenv("HOME", "/tmp/fakehome") + cfg := config{} + err := env.Parse(&cfg) + fmt.Println(err) + // Output: Env tag option option1 not supported. +} diff --git a/vendor/github.com/caarlos0/env/examples/first.go b/vendor/github.com/caarlos0/env/examples/first.go new file mode 100644 index 0000000..fbf3959 --- /dev/null +++ b/vendor/github.com/caarlos0/env/examples/first.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/caarlos0/env" +) + +type config struct { + Home string `env:"HOME"` + Port int `env:"PORT" envDefault:"3000"` + IsProduction bool `env:"PRODUCTION"` + Hosts []string `env:"HOSTS" envSeparator:":"` + Duration time.Duration `env:"DURATION"` + ExampleFoo Foo `env:"EXAMPLE_FOO"` +} + +type Foo struct { + Name string +} + +func main() { + cfg := config{} + + // Parse for built-in types + if err := env.Parse(&cfg); err != nil { + log.Fatal("Unable to parse envs: ", err) + } + + // OR w/ a custom parser for `Foo` + // + // if err := env.ParseWithFuncs(&cfg, env.CustomParsers{ + // reflect.TypeOf(Foo{}): fooParser, + // }); err != nil { + // log.Fatal("Unable to parse envs: ", err) + // } + + fmt.Printf("%+v\n", cfg) +} + +func fooParser(value string) (interface{}, error) { + return Foo{ + Name: strings.ToUpper(value), + }, nil +} diff --git a/vendor/github.com/caarlos0/env/parsers/README.md b/vendor/github.com/caarlos0/env/parsers/README.md new file mode 100644 index 0000000..13dbccf --- /dev/null +++ b/vendor/github.com/caarlos0/env/parsers/README.md @@ -0,0 +1,35 @@ +parsers +======= +This directory contains pre-built, custom parsers that can be used with `env.ParseWithFuncs` +to facilitate the parsing of envs that are not basic types. + +Example Usage: + +```golang +package main + +import ( + "fmt" + "log" + "net/url" + + "github.com/caarlos0/env" + "github.com/caarlos0/env/parsers" +) + +type config struct { + ExampleURL url.URL `env:"EXAMPLE_URL" envDefault:"https://google.com"` +} + +func main() { + cfg := config{} + + if err := env.ParseWithFuncs(&cfg, env.CustomParsers{ + parsers.URLType: parsers.URLFunc, + }); err != nil { + log.Fatal("Unable to parse envs: ", err) + } + + fmt.Printf("Scheme: %v Host: %v\n", cfg.ExampleURL.Scheme, cfg.ExampleURL.Host) +} +``` \ No newline at end of file diff --git a/vendor/github.com/caarlos0/env/parsers/parsers.go b/vendor/github.com/caarlos0/env/parsers/parsers.go new file mode 100644 index 0000000..332babf --- /dev/null +++ b/vendor/github.com/caarlos0/env/parsers/parsers.go @@ -0,0 +1,23 @@ +// Package parsers contains custom parser funcs for common, non-built-in types +package parsers + +import ( + "fmt" + "net/url" + "reflect" +) + +var ( + // URLType is a helper var that represents the `reflect.Type`` of `url.URL` + URLType = reflect.TypeOf(url.URL{}) +) + +// URLFunc is a basic parser for the url.URL type that should be used with `env.ParseWithFuncs()` +func URLFunc(v string) (interface{}, error) { + u, err := url.Parse(v) + if err != nil { + return nil, fmt.Errorf("Unable to complete URL parse: %v", err) + } + + return *u, nil +}