diff --git a/accounts/pkg/command/root.go b/accounts/pkg/command/root.go index b2f4740b1c4..c6d70c96dbb 100644 --- a/accounts/pkg/command/root.go +++ b/accounts/pkg/command/root.go @@ -7,10 +7,10 @@ import ( "github.com/owncloud/ocis/accounts/pkg/config" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis-accounts command. diff --git a/glauth/pkg/command/root.go b/glauth/pkg/command/root.go index e46b8c014c6..5fe6aefdc94 100644 --- a/glauth/pkg/command/root.go +++ b/glauth/pkg/command/root.go @@ -6,10 +6,10 @@ import ( "github.com/owncloud/ocis/glauth/pkg/config" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis-glauth command. diff --git a/go.mod b/go.mod index b2e91d60df8..e635c55660d 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,6 @@ require ( github.com/stretchr/testify v1.7.0 github.com/thejerf/suture/v4 v4.0.1 github.com/urfave/cli/v2 v2.3.0 - github.com/wkloucek/envdecode v0.0.0-20211216135343-360f0d3c2679 go-micro.dev/v4 v4.4.0 go.opencensus.io v0.23.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 diff --git a/go.sum b/go.sum index 8a9d9fdaf6f..6276c37d4ad 100644 --- a/go.sum +++ b/go.sum @@ -1294,8 +1294,6 @@ github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgq github.com/vultr/govultr/v2 v2.0.0/go.mod h1:2PsEeg+gs3p/Fo5Pw8F9mv+DUBEOlrNZ8GmCTGmhOhs= github.com/wk8/go-ordered-map v0.2.0 h1:KlvGyHstD1kkGZkPtHCyCfRYS0cz84uk6rrW/Dnhdtk= github.com/wk8/go-ordered-map v0.2.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk= -github.com/wkloucek/envdecode v0.0.0-20211216135343-360f0d3c2679 h1:aFJVdr5Lo6QrfgW4nlmguvATkSp+iOfIg6rcdTfu9eM= -github.com/wkloucek/envdecode v0.0.0-20211216135343-360f0d3c2679/go.mod h1:lEir1NV8XGJ16mCsne3GrW6MbiQyhf5WUk55kvu9rYs= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= diff --git a/graph-explorer/pkg/command/root.go b/graph-explorer/pkg/command/root.go index 034fb7ea243..6b0fbedf8fd 100644 --- a/graph-explorer/pkg/command/root.go +++ b/graph-explorer/pkg/command/root.go @@ -6,10 +6,10 @@ import ( "github.com/owncloud/ocis/graph-explorer/pkg/config" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the graph-explorer command. diff --git a/graph/pkg/command/root.go b/graph/pkg/command/root.go index 5df88df4633..565aa710fb9 100644 --- a/graph/pkg/command/root.go +++ b/graph/pkg/command/root.go @@ -4,8 +4,8 @@ import ( "context" "os" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/thejerf/suture/v4" - "github.com/wkloucek/envdecode" "github.com/owncloud/ocis/graph/pkg/config" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" diff --git a/idp/pkg/command/root.go b/idp/pkg/command/root.go index 85fda3c5218..c59652ff43d 100644 --- a/idp/pkg/command/root.go +++ b/idp/pkg/command/root.go @@ -6,10 +6,10 @@ import ( "github.com/owncloud/ocis/idp/pkg/config" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis-idp command. diff --git a/ocis-pkg/config/envdecode/LICENSE b/ocis-pkg/config/envdecode/LICENSE new file mode 100644 index 00000000000..5869b24ec6a --- /dev/null +++ b/ocis-pkg/config/envdecode/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Joe Shaw + +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/ocis-pkg/config/envdecode/README.md b/ocis-pkg/config/envdecode/README.md new file mode 100644 index 00000000000..8e7ae860e27 --- /dev/null +++ b/ocis-pkg/config/envdecode/README.md @@ -0,0 +1,88 @@ +`envdecode` is a Go package for populating structs from environment +variables. It's basically a fork of https://github.com/joeshaw/envdecode, +but changed to support multiple environment variables (precedence). + +`envdecode` uses struct tags to map environment variables to fields, +allowing you you use any names you want for environment variables. +`envdecode` will recurse into nested structs, including pointers to +nested structs, but it will not allocate new pointers to structs. + +## API + +Full API docs are available on +[godoc.org](https://godoc.org/github.com/owncloud/ocis/ocis-pkg/config/envdecode). + +Define a struct with `env` struct tags: + +```go +type Config struct { + Hostname string `env:"SERVER_HOSTNAME,default=localhost"` + Port uint16 `env:"HTTP_PORT;SERVER_PORT,default=8080"` + + AWS struct { + ID string `env:"AWS_ACCESS_KEY_ID"` + Secret string `env:"AWS_SECRET_ACCESS_KEY,required"` + SnsTopics []string `env:"AWS_SNS_TOPICS"` + } + + Timeout time.Duration `env:"TIMEOUT,default=1m,strict"` +} +``` + +Fields _must be exported_ (i.e. begin with a capital letter) in order +for `envdecode` to work with them. An error will be returned if a +struct with no exported fields is decoded (including one that contains +no `env` tags at all). +Default values may be provided by appending ",default=value" to the +struct tag. Required values may be marked by appending ",required" to the +struct tag. Strict values may be marked by appending ",strict" which will +return an error on Decode if there is an error while parsing. + +Then call `envdecode.Decode`: + +```go +var cfg Config +err := envdecode.Decode(&cfg) +``` + +If you want all fields to act `strict`, you may use `envdecode.StrictDecode`: + +```go +var cfg Config +err := envdecode.StrictDecode(&cfg) +``` + +All parse errors will fail fast and return an error in this mode. + +## Supported types + +- Structs (and pointer to structs) +- Slices of below defined types, separated by semicolon +- `bool` +- `float32`, `float64` +- `int`, `int8`, `int16`, `int32`, `int64` +- `uint`, `uint8`, `uint16`, `uint32`, `uint64` +- `string` +- `time.Duration`, using the [`time.ParseDuration()` format](http://golang.org/pkg/time/#ParseDuration) +- `*url.URL`, using [`url.Parse()`](https://godoc.org/net/url#Parse) +- Types those implement a `Decoder` interface + +## Custom `Decoder` + +If you want a field to be decoded with custom behavior, you may implement the interface `Decoder` for the filed type. + +```go +type Config struct { + IPAddr IP `env:"IP_ADDR"` +} + +type IP net.IP + +// Decode implements the interface `envdecode.Decoder` +func (i *IP) Decode(repl string) error { + *i = net.ParseIP(repl) + return nil +} +``` + +`Decoder` is the interface implemented by an object that can decode an environment variable string representation of itself. diff --git a/ocis-pkg/config/envdecode/envdecode.go b/ocis-pkg/config/envdecode/envdecode.go new file mode 100644 index 00000000000..d97d698988e --- /dev/null +++ b/ocis-pkg/config/envdecode/envdecode.go @@ -0,0 +1,431 @@ +// Package envdecode is a package for populating structs from environment +// variables, using struct tags. +package envdecode + +import ( + "encoding" + "errors" + "fmt" + "log" + "net/url" + "os" + "reflect" + "sort" + "strconv" + "strings" + "time" +) + +// ErrInvalidTarget indicates that the target value passed to +// Decode is invalid. Target must be a non-nil pointer to a struct. +var ErrInvalidTarget = errors.New("target must be non-nil pointer to struct that has at least one exported field with a valid env tag.") +var ErrNoTargetFieldsAreSet = errors.New("none of the target fields were set from environment variables") + +// FailureFunc is called when an error is encountered during a MustDecode +// operation. It prints the error and terminates the process. +// +// This variable can be assigned to another function of the user-programmer's +// design, allowing for graceful recovery of the problem, such as loading +// from a backup configuration file. +var FailureFunc = func(err error) { + log.Fatalf("envdecode: an error was encountered while decoding: %v\n", err) +} + +// Decoder is the interface implemented by an object that can decode an +// environment variable string representation of itself. +type Decoder interface { + Decode(string) error +} + +// Decode environment variables into the provided target. The target +// must be a non-nil pointer to a struct. Fields in the struct must +// be exported, and tagged with an "env" struct tag with a value +// containing the name of the environment variable. An error is +// returned if there are no exported members tagged. +// +// Default values may be provided by appending ",default=value" to the +// struct tag. Required values may be marked by appending ",required" +// to the struct tag. It is an error to provide both "default" and +// "required". Strict values may be marked by appending ",strict" which +// will return an error on Decode if there is an error while parsing. +// If everything must be strict, consider using StrictDecode instead. +// +// All primitive types are supported, including bool, floating point, +// signed and unsigned integers, and string. Boolean and numeric +// types are decoded using the standard strconv Parse functions for +// those types. Structs and pointers to structs are decoded +// recursively. time.Duration is supported via the +// time.ParseDuration() function and *url.URL is supported via the +// url.Parse() function. Slices are supported for all above mentioned +// primitive types. Semicolon is used as delimiter in environment variables. +func Decode(target interface{}) error { + nFields, err := decode(target, false) + if err != nil { + return err + } + + // if we didn't do anything - the user probably did something + // wrong like leave all fields unexported. + if nFields == 0 { + return ErrNoTargetFieldsAreSet + } + + return nil +} + +// StrictDecode is similar to Decode except all fields will have an implicit +// ",strict" on all fields. +func StrictDecode(target interface{}) error { + nFields, err := decode(target, true) + if err != nil { + return err + } + + // if we didn't do anything - the user probably did something + // wrong like leave all fields unexported. + if nFields == 0 { + return ErrInvalidTarget + } + + return nil +} + +func decode(target interface{}, strict bool) (int, error) { + s := reflect.ValueOf(target) + if s.Kind() != reflect.Ptr || s.IsNil() { + return 0, ErrInvalidTarget + } + + s = s.Elem() + if s.Kind() != reflect.Struct { + return 0, ErrInvalidTarget + } + + t := s.Type() + setFieldCount := 0 + for i := 0; i < s.NumField(); i++ { + // Localize the umbrella `strict` value to the specific field. + strict := strict + + f := s.Field(i) + + switch f.Kind() { + case reflect.Ptr: + if f.Elem().Kind() != reflect.Struct { + break + } + + f = f.Elem() + fallthrough + + case reflect.Struct: + if !f.Addr().CanInterface() { + continue + } + + ss := f.Addr().Interface() + _, custom := ss.(Decoder) + if custom { + break + } + + n, err := decode(ss, strict) + if err != nil { + return 0, err + } + setFieldCount += n + } + + if !f.CanSet() { + continue + } + + tag := t.Field(i).Tag.Get("env") + if tag == "" { + continue + } + + parts := strings.Split(tag, ",") + overrides := strings.Split(parts[0], `;`) + + var env string + for _, override := range overrides { + v := os.Getenv(override) + if v != "" { + env = v + } + } + + required := false + hasDefault := false + defaultValue := "" + + for _, o := range parts[1:] { + if !required { + required = strings.HasPrefix(o, "required") + } + if strings.HasPrefix(o, "default=") { + hasDefault = true + defaultValue = o[8:] + } + if !strict { + strict = strings.HasPrefix(o, "strict") + } + } + + if required && hasDefault { + panic(`envdecode: "default" and "required" may not be specified in the same annotation`) + } + if env == "" && required { + return 0, fmt.Errorf("the environment variable \"%s\" is missing", parts[0]) + } + if env == "" { + env = defaultValue + } + if env == "" { + continue + } + + setFieldCount++ + + unmarshaler, implementsUnmarshaler := f.Addr().Interface().(encoding.TextUnmarshaler) + decoder, implmentsDecoder := f.Addr().Interface().(Decoder) + if implmentsDecoder { + if err := decoder.Decode(env); err != nil { + return 0, err + } + } else if implementsUnmarshaler { + if err := unmarshaler.UnmarshalText([]byte(env)); err != nil { + return 0, err + } + } else if f.Kind() == reflect.Slice { + decodeSlice(&f, env) + } else { + if err := decodePrimitiveType(&f, env); err != nil && strict { + return 0, err + } + } + } + + return setFieldCount, nil +} + +func decodeSlice(f *reflect.Value, env string) { + parts := strings.Split(env, ";") + + values := parts[:0] + for _, x := range parts { + if x != "" { + values = append(values, strings.TrimSpace(x)) + } + } + + valuesCount := len(values) + slice := reflect.MakeSlice(f.Type(), valuesCount, valuesCount) + if valuesCount > 0 { + for i := 0; i < valuesCount; i++ { + e := slice.Index(i) + decodePrimitiveType(&e, values[i]) + } + } + + f.Set(slice) +} + +func decodePrimitiveType(f *reflect.Value, env string) error { + switch f.Kind() { + case reflect.Bool: + v, err := strconv.ParseBool(env) + if err != nil { + return err + } + f.SetBool(v) + + case reflect.Float32, reflect.Float64: + bits := f.Type().Bits() + v, err := strconv.ParseFloat(env, bits) + if err != nil { + return err + } + f.SetFloat(v) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if t := f.Type(); t.PkgPath() == "time" && t.Name() == "Duration" { + v, err := time.ParseDuration(env) + if err != nil { + return err + } + f.SetInt(int64(v)) + } else { + bits := f.Type().Bits() + v, err := strconv.ParseInt(env, 0, bits) + if err != nil { + return err + } + f.SetInt(v) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + bits := f.Type().Bits() + v, err := strconv.ParseUint(env, 0, bits) + if err != nil { + return err + } + f.SetUint(v) + + case reflect.String: + f.SetString(env) + + case reflect.Ptr: + if t := f.Type().Elem(); t.Kind() == reflect.Struct && t.PkgPath() == "net/url" && t.Name() == "URL" { + v, err := url.Parse(env) + if err != nil { + return err + } + f.Set(reflect.ValueOf(v)) + } + } + return nil +} + +// MustDecode calls Decode and terminates the process if any errors +// are encountered. +func MustDecode(target interface{}) { + err := Decode(target) + if err != nil { + FailureFunc(err) + } +} + +// MustStrictDecode calls StrictDecode and terminates the process if any errors +// are encountered. +func MustStrictDecode(target interface{}) { + err := StrictDecode(target) + if err != nil { + FailureFunc(err) + } +} + +//// Configuration info for Export + +type ConfigInfo struct { + Field string + EnvVar string + Value string + DefaultValue string + HasDefault bool + Required bool + UsesEnv bool +} + +type ConfigInfoSlice []*ConfigInfo + +func (c ConfigInfoSlice) Less(i, j int) bool { + return c[i].EnvVar < c[j].EnvVar +} +func (c ConfigInfoSlice) Len() int { + return len(c) +} +func (c ConfigInfoSlice) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +// Returns a list of final configuration metadata sorted by envvar name +func Export(target interface{}) ([]*ConfigInfo, error) { + s := reflect.ValueOf(target) + if s.Kind() != reflect.Ptr || s.IsNil() { + return nil, ErrInvalidTarget + } + + cfg := []*ConfigInfo{} + + s = s.Elem() + if s.Kind() != reflect.Struct { + return nil, ErrInvalidTarget + } + + t := s.Type() + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + fName := t.Field(i).Name + + fElem := f + if f.Kind() == reflect.Ptr { + fElem = f.Elem() + } + if fElem.Kind() == reflect.Struct { + ss := fElem.Addr().Interface() + subCfg, err := Export(ss) + if err != ErrInvalidTarget { + f = fElem + for _, v := range subCfg { + v.Field = fmt.Sprintf("%s.%s", fName, v.Field) + cfg = append(cfg, v) + } + } + } + + tag := t.Field(i).Tag.Get("env") + if tag == "" { + continue + } + + parts := strings.Split(tag, ",") + + ci := &ConfigInfo{ + Field: fName, + EnvVar: parts[0], + UsesEnv: os.Getenv(parts[0]) != "", + } + + for _, o := range parts[1:] { + if strings.HasPrefix(o, "default=") { + ci.HasDefault = true + ci.DefaultValue = o[8:] + } else if strings.HasPrefix(o, "required") { + ci.Required = true + } + } + + if f.Kind() == reflect.Ptr && f.IsNil() { + ci.Value = "" + } else if stringer, ok := f.Interface().(fmt.Stringer); ok { + ci.Value = stringer.String() + } else { + switch f.Kind() { + case reflect.Bool: + ci.Value = strconv.FormatBool(f.Bool()) + + case reflect.Float32, reflect.Float64: + bits := f.Type().Bits() + ci.Value = strconv.FormatFloat(f.Float(), 'f', -1, bits) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + ci.Value = strconv.FormatInt(f.Int(), 10) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + ci.Value = strconv.FormatUint(f.Uint(), 10) + + case reflect.String: + ci.Value = f.String() + + case reflect.Slice: + ci.Value = fmt.Sprintf("%v", f.Interface()) + + default: + // Unable to determine string format for value + return nil, ErrInvalidTarget + } + } + + cfg = append(cfg, ci) + } + + // No configuration tags found, assume invalid input + if len(cfg) == 0 { + return nil, ErrInvalidTarget + } + + sort.Sort(ConfigInfoSlice(cfg)) + + return cfg, nil +} diff --git a/ocis-pkg/config/envdecode/envdecode_test.go b/ocis-pkg/config/envdecode/envdecode_test.go new file mode 100644 index 00000000000..bf6fd3352b1 --- /dev/null +++ b/ocis-pkg/config/envdecode/envdecode_test.go @@ -0,0 +1,795 @@ +package envdecode + +import ( + "encoding/json" + "fmt" + "math" + "net/url" + "os" + "reflect" + "sort" + "strconv" + "sync" + "testing" + "time" +) + +type nested struct { + String string `env:"TEST_STRING"` +} + +type testConfig struct { + String string `env:"TEST_STRING"` + Int64 int64 `env:"TEST_INT64"` + Uint16 uint16 `env:"TEST_UINT16"` + Float64 float64 `env:"TEST_FLOAT64"` + Bool bool `env:"TEST_BOOL"` + Duration time.Duration `env:"TEST_DURATION"` + URL *url.URL `env:"TEST_URL"` + + StringSlice []string `env:"TEST_STRING_SLICE"` + Int64Slice []int64 `env:"TEST_INT64_SLICE"` + Uint16Slice []uint16 `env:"TEST_UINT16_SLICE"` + Float64Slice []float64 `env:"TEST_FLOAT64_SLICE"` + BoolSlice []bool `env:"TEST_BOOL_SLICE"` + DurationSlice []time.Duration `env:"TEST_DURATION_SLICE"` + URLSlice []*url.URL `env:"TEST_URL_SLICE"` + + UnsetString string `env:"TEST_UNSET_STRING"` + UnsetInt64 int64 `env:"TEST_UNSET_INT64"` + UnsetDuration time.Duration `env:"TEST_UNSET_DURATION"` + UnsetURL *url.URL `env:"TEST_UNSET_URL"` + UnsetSlice []string `env:"TEST_UNSET_SLICE"` + + InvalidInt64 int64 `env:"TEST_INVALID_INT64"` + + UnusedField string + unexportedField string + + IgnoredPtr *bool `env:"TEST_BOOL"` + + Nested nested + NestedPtr *nested + + DecoderStruct decoderStruct `env:"TEST_DECODER_STRUCT"` + DecoderStructPtr *decoderStruct `env:"TEST_DECODER_STRUCT_PTR"` + + DecoderString decoderString `env:"TEST_DECODER_STRING"` + + UnmarshalerNumber unmarshalerNumber `env:"TEST_UNMARSHALER_NUMBER"` + + DefaultInt int `env:"TEST_UNSET,asdf=asdf,default=1234"` + DefaultSliceInt []int `env:"TEST_UNSET,asdf=asdf,default=1;2;3"` + DefaultDuration time.Duration `env:"TEST_UNSET,asdf=asdf,default=24h"` + DefaultURL *url.URL `env:"TEST_UNSET,default=http://example.com"` + + cantInterfaceField sync.Mutex +} + +type testConfigNoSet struct { + Some string `env:"TEST_THIS_ENV_WILL_NOT_BE_SET"` +} + +type testConfigRequired struct { + Required string `env:"TEST_REQUIRED,required"` +} + +type testConfigRequiredDefault struct { + RequiredDefault string `env:"TEST_REQUIRED_DEFAULT,required,default=test"` +} + +type testConfigOverride struct { + OverrideString string `env:"TEST_OVERRIDE_A;TEST_OVERRIDE_B,default=override_default"` +} + +type testNoExportedFields struct { + aString string `env:"TEST_STRING"` + anInt64 int64 `env:"TEST_INT64"` + aUint16 uint16 `env:"TEST_UINT16"` + aFloat64 float64 `env:"TEST_FLOAT64"` + aBool bool `env:"TEST_BOOL"` +} + +type testNoTags struct { + String string +} + +type decoderStruct struct { + String string +} + +func (d *decoderStruct) Decode(env string) error { + return json.Unmarshal([]byte(env), &d) +} + +type decoderString string + +func (d *decoderString) Decode(env string) error { + r, l := []rune(env), len(env) + + for i := 0; i < l/2; i++ { + r[i], r[l-1-i] = r[l-1-i], r[i] + } + + *d = decoderString(r) + return nil +} + +type unmarshalerNumber uint8 + +func (o *unmarshalerNumber) UnmarshalText(raw []byte) error { + n, err := strconv.ParseUint(string(raw), 8, 8) // parse text as octal number + if err != nil { + return err + } + *o = unmarshalerNumber(n) + return nil +} + +func TestDecode(t *testing.T) { + int64Val := int64(-(1 << 50)) + int64AsString := fmt.Sprintf("%d", int64Val) + piAsString := fmt.Sprintf("%.48f", math.Pi) + + os.Setenv("TEST_STRING", "foo") + os.Setenv("TEST_INT64", int64AsString) + os.Setenv("TEST_UINT16", "60000") + os.Setenv("TEST_FLOAT64", piAsString) + os.Setenv("TEST_BOOL", "true") + os.Setenv("TEST_DURATION", "10m") + os.Setenv("TEST_URL", "https://example.com") + os.Setenv("TEST_INVALID_INT64", "asdf") + os.Setenv("TEST_STRING_SLICE", "foo;bar") + os.Setenv("TEST_INT64_SLICE", int64AsString+";"+int64AsString) + os.Setenv("TEST_UINT16_SLICE", "60000;50000") + os.Setenv("TEST_FLOAT64_SLICE", piAsString+";"+piAsString) + os.Setenv("TEST_BOOL_SLICE", "true; false; true") + os.Setenv("TEST_DURATION_SLICE", "10m; 20m") + os.Setenv("TEST_URL_SLICE", "https://example.com") + os.Setenv("TEST_DECODER_STRUCT", "{\"string\":\"foo\"}") + os.Setenv("TEST_DECODER_STRUCT_PTR", "{\"string\":\"foo\"}") + os.Setenv("TEST_DECODER_STRING", "oof") + os.Setenv("TEST_UNMARSHALER_NUMBER", "07") + + var tc testConfig + tc.NestedPtr = &nested{} + tc.DecoderStructPtr = &decoderStruct{} + + err := Decode(&tc) + if err != nil { + t.Fatal(err) + } + + if tc.String != "foo" { + t.Fatalf(`Expected "foo", got "%s"`, tc.String) + } + + if tc.Int64 != -(1 << 50) { + t.Fatalf("Expected %d, got %d", -(1 << 50), tc.Int64) + } + + if tc.Uint16 != 60000 { + t.Fatalf("Expected 60000, got %d", tc.Uint16) + } + + if tc.Float64 != math.Pi { + t.Fatalf("Expected %.48f, got %.48f", math.Pi, tc.Float64) + } + + if !tc.Bool { + t.Fatal("Expected true, got false") + } + + duration, _ := time.ParseDuration("10m") + if tc.Duration != duration { + t.Fatalf("Expected %d, got %d", duration, tc.Duration) + } + + if tc.URL == nil { + t.Fatalf("Expected https://example.com, got nil") + } else if tc.URL.String() != "https://example.com" { + t.Fatalf("Expected https://example.com, got %s", tc.URL.String()) + } + + expectedStringSlice := []string{"foo", "bar"} + if !reflect.DeepEqual(tc.StringSlice, expectedStringSlice) { + t.Fatalf("Expected %s, got %s", expectedStringSlice, tc.StringSlice) + } + + expectedInt64Slice := []int64{int64Val, int64Val} + if !reflect.DeepEqual(tc.Int64Slice, expectedInt64Slice) { + t.Fatalf("Expected %#v, got %#v", expectedInt64Slice, tc.Int64Slice) + } + + expectedUint16Slice := []uint16{60000, 50000} + if !reflect.DeepEqual(tc.Uint16Slice, expectedUint16Slice) { + t.Fatalf("Expected %#v, got %#v", expectedUint16Slice, tc.Uint16Slice) + } + + expectedFloat64Slice := []float64{math.Pi, math.Pi} + if !reflect.DeepEqual(tc.Float64Slice, expectedFloat64Slice) { + t.Fatalf("Expected %#v, got %#v", expectedFloat64Slice, tc.Float64Slice) + } + + expectedBoolSlice := []bool{true, false, true} + if !reflect.DeepEqual(tc.BoolSlice, expectedBoolSlice) { + t.Fatalf("Expected %#v, got %#v", expectedBoolSlice, tc.BoolSlice) + } + + duration2, _ := time.ParseDuration("20m") + expectedDurationSlice := []time.Duration{duration, duration2} + if !reflect.DeepEqual(tc.DurationSlice, expectedDurationSlice) { + t.Fatalf("Expected %s, got %s", expectedDurationSlice, tc.DurationSlice) + } + + urlVal, _ := url.Parse("https://example.com") + expectedUrlSlice := []*url.URL{urlVal} + if !reflect.DeepEqual(tc.URLSlice, expectedUrlSlice) { + t.Fatalf("Expected %s, got %s", expectedUrlSlice, tc.URLSlice) + } + + if tc.UnsetString != "" { + t.Fatal("Got non-empty string unexpectedly") + } + + if tc.UnsetInt64 != 0 { + t.Fatal("Got non-zero int unexpectedly") + } + + if tc.UnsetDuration != time.Duration(0) { + t.Fatal("Got non-zero time.Duration unexpectedly") + } + + if tc.UnsetURL != nil { + t.Fatal("Got non-zero *url.URL unexpectedly") + } + + if len(tc.UnsetSlice) > 0 { + t.Fatal("Got not-empty string slice unexpectedly") + } + + if tc.InvalidInt64 != 0 { + t.Fatal("Got non-zero int unexpectedly") + } + + if tc.UnusedField != "" { + t.Fatal("Expected empty field") + } + + if tc.unexportedField != "" { + t.Fatal("Expected empty field") + } + + if tc.IgnoredPtr != nil { + t.Fatal("Expected nil pointer") + } + + if tc.Nested.String != "foo" { + t.Fatalf(`Expected "foo", got "%s"`, tc.Nested.String) + } + + if tc.NestedPtr.String != "foo" { + t.Fatalf(`Expected "foo", got "%s"`, tc.NestedPtr.String) + } + + if tc.DefaultInt != 1234 { + t.Fatalf("Expected 1234, got %d", tc.DefaultInt) + } + + expectedDefaultSlice := []int{1, 2, 3} + if !reflect.DeepEqual(tc.DefaultSliceInt, expectedDefaultSlice) { + t.Fatalf("Expected %d, got %d", expectedDefaultSlice, tc.DefaultSliceInt) + } + + defaultDuration, _ := time.ParseDuration("24h") + if tc.DefaultDuration != defaultDuration { + t.Fatalf("Expected %d, got %d", defaultDuration, tc.DefaultInt) + } + + if tc.DefaultURL.String() != "http://example.com" { + t.Fatalf("Expected http://example.com, got %s", tc.DefaultURL.String()) + } + + if tc.DecoderStruct.String != "foo" { + t.Fatalf("Expected foo, got %s", tc.DecoderStruct.String) + } + + if tc.DecoderStructPtr.String != "foo" { + t.Fatalf("Expected foo, got %s", tc.DecoderStructPtr.String) + } + + if tc.DecoderString != "foo" { + t.Fatalf("Expected foo, got %s", tc.DecoderString) + } + + if tc.UnmarshalerNumber != 07 { + t.Fatalf("Expected 07, got %04o", tc.UnmarshalerNumber) + } + + os.Setenv("TEST_REQUIRED", "required") + var tcr testConfigRequired + + err = Decode(&tcr) + if err != nil { + t.Fatal(err) + } + + if tcr.Required != "required" { + t.Fatalf("Expected \"required\", got %s", tcr.Required) + } + + _, err = Export(&tcr) + if err != nil { + t.Fatal(err) + } + + var tco testConfigOverride + err = Decode(&tco) + if err != nil { + t.Fatal(err) + } + + if tco.OverrideString != "override_default" { + t.Fatalf(`Expected "override_default" but got %s`, tco.OverrideString) + } + + os.Setenv("TEST_OVERRIDE_A", "override_a") + + tco = testConfigOverride{} + err = Decode(&tco) + if err != nil { + t.Fatal(err) + } + + if tco.OverrideString != "override_a" { + t.Fatalf(`Expected "override_a" but got %s`, tco.OverrideString) + } + + os.Setenv("TEST_OVERRIDE_B", "override_b") + + tco = testConfigOverride{} + err = Decode(&tco) + if err != nil { + t.Fatal(err) + } + + if tco.OverrideString != "override_b" { + t.Fatalf(`Expected "override_b" but got %s`, tco.OverrideString) + } +} + +func TestDecodeErrors(t *testing.T) { + var b bool + err := Decode(&b) + if err != ErrInvalidTarget { + t.Fatal("Should have gotten an error decoding into a bool") + } + + var tc testConfig + err = Decode(tc) + if err != ErrInvalidTarget { + t.Fatal("Should have gotten an error decoding into a non-pointer") + } + + var tcp *testConfig + err = Decode(tcp) + if err != ErrInvalidTarget { + t.Fatal("Should have gotten an error decoding to a nil pointer") + } + + var tnt testNoTags + err = Decode(&tnt) + if err != ErrNoTargetFieldsAreSet { + t.Fatal("Should have gotten an error decoding a struct with no tags") + } + + var tcni testNoExportedFields + err = Decode(&tcni) + if err != ErrNoTargetFieldsAreSet { + t.Fatal("Should have gotten an error decoding a struct with no unexported fields") + } + + var tcr testConfigRequired + os.Clearenv() + err = Decode(&tcr) + if err == nil { + t.Fatal("An error was expected but recieved:", err) + } + + var tcns testConfigNoSet + err = Decode(&tcns) + if err != ErrNoTargetFieldsAreSet { + t.Fatal("Should have gotten an error decoding when no env variables are set") + } + + missing := false + FailureFunc = func(err error) { + missing = true + } + MustDecode(&tcr) + if !missing { + t.Fatal("The FailureFunc should have been called but it was not") + } + + var tcrd testConfigRequiredDefault + defer func() { + if r := recover(); r != nil { + } + }() + err = Decode(&tcrd) + t.Fatal("This should not have been reached. A panic should have occured.") +} + +func TestOnlyNested(t *testing.T) { + os.Setenv("TEST_STRING", "foo") + + // No env vars in the outer level are ok, as long as they're + // in the inner struct. + var o struct { + Inner nested + } + if err := Decode(&o); err != nil { + t.Fatalf("Expected no error, got %s", err) + } + + // No env vars in the inner levels are ok, as long as they're + // in the outer struct. + var o2 struct { + Inner noConfig + X string `env:"TEST_STRING"` + } + if err := Decode(&o2); err != nil { + t.Fatalf("Expected no error, got %s", err) + } + + // No env vars in either outer or inner levels should result + // in error + var o3 struct { + Inner noConfig + } + if err := Decode(&o3); err != ErrNoTargetFieldsAreSet { + t.Fatalf("Expected ErrInvalidTarget, got %s", err) + } +} + +func ExampleDecode() { + type Example struct { + // A string field, without any default + String string `env:"EXAMPLE_STRING"` + + // A uint16 field, with a default value of 100 + Uint16 uint16 `env:"EXAMPLE_UINT16,default=100"` + } + + os.Setenv("EXAMPLE_STRING", "an example!") + + var e Example + err := Decode(&e) + if err != nil { + panic(err) + } + + // If TEST_STRING is set, e.String will contain its value + fmt.Println(e.String) + + // If TEST_UINT16 is set, e.Uint16 will contain its value. + // Otherwise, it will contain the default value, 100. + fmt.Println(e.Uint16) + + // Output: + // an example! + // 100 +} + +//// Export tests + +type testConfigExport struct { + String string `env:"TEST_STRING"` + Int64 int64 `env:"TEST_INT64"` + Uint16 uint16 `env:"TEST_UINT16"` + Float64 float64 `env:"TEST_FLOAT64"` + Bool bool `env:"TEST_BOOL"` + Duration time.Duration `env:"TEST_DURATION"` + URL *url.URL `env:"TEST_URL"` + + StringSlice []string `env:"TEST_STRING_SLICE"` + + UnsetString string `env:"TEST_UNSET_STRING"` + UnsetInt64 int64 `env:"TEST_UNSET_INT64"` + UnsetDuration time.Duration `env:"TEST_UNSET_DURATION"` + UnsetURL *url.URL `env:"TEST_UNSET_URL"` + + UnusedField string + unexportedField string + + IgnoredPtr *bool `env:"TEST_IGNORED_POINTER"` + + Nested nestedConfigExport + NestedPtr *nestedConfigExportPointer + NestedPtrUnset *nestedConfigExportPointer + + NestedTwice nestedTwiceConfig + + NoConfig noConfig + NoConfigPtr *noConfig + NoConfigPtrSet *noConfig + + RequiredInt int `env:"TEST_REQUIRED_INT,required"` + + DefaultBool bool `env:"TEST_DEFAULT_BOOL,default=true"` + DefaultInt int `env:"TEST_DEFAULT_INT,default=1234"` + DefaultDuration time.Duration `env:"TEST_DEFAULT_DURATION,default=24h"` + DefaultURL *url.URL `env:"TEST_DEFAULT_URL,default=http://example.com"` + DefaultIntSet int `env:"TEST_DEFAULT_INT_SET,default=99"` + DefaultIntSlice []int `env:"TEST_DEFAULT_INT_SLICE,default=99;33"` +} + +type nestedConfigExport struct { + String string `env:"TEST_NESTED_STRING"` +} + +type nestedConfigExportPointer struct { + String string `env:"TEST_NESTED_STRING_POINTER"` +} + +type noConfig struct { + Int int +} + +type nestedTwiceConfig struct { + Nested nestedConfigInner +} + +type nestedConfigInner struct { + String string `env:"TEST_NESTED_TWICE_STRING"` +} + +type testConfigStrict struct { + InvalidInt64Strict int64 `env:"TEST_INVALID_INT64,strict,default=1"` + InvalidInt64Implicit int64 `env:"TEST_INVALID_INT64_IMPLICIT,default=1"` + + Nested struct { + InvalidInt64Strict int64 `env:"TEST_INVALID_INT64_NESTED,strict,required"` + InvalidInt64Implicit int64 `env:"TEST_INVALID_INT64_NESTED_IMPLICIT,required"` + } +} + +func TestInvalidStrict(t *testing.T) { + cases := []struct { + decoder func(interface{}) error + rootValue string + nestedValue string + rootValueImplicit string + nestedValueImplicit string + pass bool + }{ + {Decode, "1", "1", "1", "1", true}, + {Decode, "1", "1", "1", "asdf", true}, + {Decode, "1", "1", "asdf", "1", true}, + {Decode, "1", "1", "asdf", "asdf", true}, + {Decode, "1", "asdf", "1", "1", false}, + {Decode, "asdf", "1", "1", "1", false}, + {Decode, "asdf", "asdf", "1", "1", false}, + {StrictDecode, "1", "1", "1", "1", true}, + {StrictDecode, "asdf", "1", "1", "1", false}, + {StrictDecode, "1", "asdf", "1", "1", false}, + {StrictDecode, "1", "1", "asdf", "1", false}, + {StrictDecode, "1", "1", "1", "asdf", false}, + {StrictDecode, "asdf", "asdf", "1", "1", false}, + {StrictDecode, "1", "asdf", "asdf", "1", false}, + {StrictDecode, "1", "1", "asdf", "asdf", false}, + {StrictDecode, "1", "asdf", "asdf", "asdf", false}, + {StrictDecode, "asdf", "asdf", "asdf", "asdf", false}, + } + + for _, test := range cases { + os.Setenv("TEST_INVALID_INT64", test.rootValue) + os.Setenv("TEST_INVALID_INT64_NESTED", test.nestedValue) + os.Setenv("TEST_INVALID_INT64_IMPLICIT", test.rootValueImplicit) + os.Setenv("TEST_INVALID_INT64_NESTED_IMPLICIT", test.nestedValueImplicit) + + var tc testConfigStrict + if err := test.decoder(&tc); test.pass != (err == nil) { + t.Fatalf("Have err=%s wanted pass=%v", err, test.pass) + } + } +} + +func TestExport(t *testing.T) { + testFloat64 := fmt.Sprintf("%.48f", math.Pi) + testFloat64Output := strconv.FormatFloat(math.Pi, 'f', -1, 64) + testInt64 := fmt.Sprintf("%d", -(1 << 50)) + + os.Setenv("TEST_STRING", "foo") + os.Setenv("TEST_INT64", testInt64) + os.Setenv("TEST_UINT16", "60000") + os.Setenv("TEST_FLOAT64", testFloat64) + os.Setenv("TEST_BOOL", "true") + os.Setenv("TEST_DURATION", "10m") + os.Setenv("TEST_URL", "https://example.com") + os.Setenv("TEST_STRING_SLICE", "foo;bar") + os.Setenv("TEST_NESTED_STRING", "nest_foo") + os.Setenv("TEST_NESTED_STRING_POINTER", "nest_foo_ptr") + os.Setenv("TEST_NESTED_TWICE_STRING", "nest_twice_foo") + os.Setenv("TEST_REQUIRED_INT", "101") + os.Setenv("TEST_DEFAULT_INT_SET", "102") + os.Setenv("TEST_DEFAULT_INT_SLICE", "1;2;3") + + var tc testConfigExport + tc.NestedPtr = &nestedConfigExportPointer{} + tc.NoConfigPtrSet = &noConfig{} + + err := Decode(&tc) + if err != nil { + t.Fatal(err) + } + + rc, err := Export(&tc) + if err != nil { + t.Fatal(err) + } + + expected := []*ConfigInfo{ + &ConfigInfo{ + Field: "String", + EnvVar: "TEST_STRING", + Value: "foo", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Int64", + EnvVar: "TEST_INT64", + Value: testInt64, + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Uint16", + EnvVar: "TEST_UINT16", + Value: "60000", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Float64", + EnvVar: "TEST_FLOAT64", + Value: testFloat64Output, + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Bool", + EnvVar: "TEST_BOOL", + Value: "true", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Duration", + EnvVar: "TEST_DURATION", + Value: "10m0s", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "URL", + EnvVar: "TEST_URL", + Value: "https://example.com", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "StringSlice", + EnvVar: "TEST_STRING_SLICE", + Value: "[foo bar]", + UsesEnv: true, + }, + + &ConfigInfo{ + Field: "UnsetString", + EnvVar: "TEST_UNSET_STRING", + Value: "", + }, + &ConfigInfo{ + Field: "UnsetInt64", + EnvVar: "TEST_UNSET_INT64", + Value: "0", + }, + &ConfigInfo{ + Field: "UnsetDuration", + EnvVar: "TEST_UNSET_DURATION", + Value: "0s", + }, + &ConfigInfo{ + Field: "UnsetURL", + EnvVar: "TEST_UNSET_URL", + Value: "", + }, + + &ConfigInfo{ + Field: "IgnoredPtr", + EnvVar: "TEST_IGNORED_POINTER", + Value: "", + }, + + &ConfigInfo{ + Field: "Nested.String", + EnvVar: "TEST_NESTED_STRING", + Value: "nest_foo", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "NestedPtr.String", + EnvVar: "TEST_NESTED_STRING_POINTER", + Value: "nest_foo_ptr", + UsesEnv: true, + }, + + &ConfigInfo{ + Field: "NestedTwice.Nested.String", + EnvVar: "TEST_NESTED_TWICE_STRING", + Value: "nest_twice_foo", + UsesEnv: true, + }, + + &ConfigInfo{ + Field: "RequiredInt", + EnvVar: "TEST_REQUIRED_INT", + Value: "101", + UsesEnv: true, + Required: true, + }, + + &ConfigInfo{ + Field: "DefaultBool", + EnvVar: "TEST_DEFAULT_BOOL", + Value: "true", + DefaultValue: "true", + HasDefault: true, + }, + &ConfigInfo{ + Field: "DefaultInt", + EnvVar: "TEST_DEFAULT_INT", + Value: "1234", + DefaultValue: "1234", + HasDefault: true, + }, + &ConfigInfo{ + Field: "DefaultDuration", + EnvVar: "TEST_DEFAULT_DURATION", + Value: "24h0m0s", + DefaultValue: "24h", + HasDefault: true, + }, + &ConfigInfo{ + Field: "DefaultURL", + EnvVar: "TEST_DEFAULT_URL", + Value: "http://example.com", + DefaultValue: "http://example.com", + HasDefault: true, + }, + &ConfigInfo{ + Field: "DefaultIntSet", + EnvVar: "TEST_DEFAULT_INT_SET", + Value: "102", + DefaultValue: "99", + HasDefault: true, + UsesEnv: true, + }, + &ConfigInfo{ + Field: "DefaultIntSlice", + EnvVar: "TEST_DEFAULT_INT_SLICE", + Value: "[1 2 3]", + DefaultValue: "99;33", + HasDefault: true, + UsesEnv: true, + }, + } + + sort.Sort(ConfigInfoSlice(expected)) + + if len(rc) != len(expected) { + t.Fatalf("Have %d results, expected %d", len(rc), len(expected)) + } + + for n, v := range rc { + ci := expected[n] + if *ci != *v { + t.Fatalf("have %+v, expected %+v", v, ci) + } + } +} diff --git a/ocis/pkg/command/root.go b/ocis/pkg/command/root.go index 09d7e63fbf3..232c18ab243 100644 --- a/ocis/pkg/command/root.go +++ b/ocis/pkg/command/root.go @@ -5,10 +5,10 @@ import ( "github.com/owncloud/ocis/ocis-pkg/config" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis command. diff --git a/ocs/pkg/command/root.go b/ocs/pkg/command/root.go index 8d24d1bd6a2..994e5e8898c 100644 --- a/ocs/pkg/command/root.go +++ b/ocs/pkg/command/root.go @@ -5,11 +5,11 @@ import ( "os" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/ocs/pkg/config" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis-ocs command. diff --git a/proxy/pkg/command/root.go b/proxy/pkg/command/root.go index 0d20391354e..7bd07dfde84 100644 --- a/proxy/pkg/command/root.go +++ b/proxy/pkg/command/root.go @@ -5,11 +5,11 @@ import ( "os" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/proxy/pkg/config" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis-proxy command. diff --git a/settings/pkg/command/root.go b/settings/pkg/command/root.go index bbddc89444b..c6091ef905a 100644 --- a/settings/pkg/command/root.go +++ b/settings/pkg/command/root.go @@ -5,11 +5,11 @@ import ( "os" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/settings/pkg/config" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis-settings command. diff --git a/store/pkg/command/root.go b/store/pkg/command/root.go index d10f4cb4dd6..be613a9f756 100644 --- a/store/pkg/command/root.go +++ b/store/pkg/command/root.go @@ -5,11 +5,11 @@ import ( "os" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/store/pkg/config" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis-store command. diff --git a/thumbnails/pkg/command/root.go b/thumbnails/pkg/command/root.go index 4e24303e589..4236c1098b8 100644 --- a/thumbnails/pkg/command/root.go +++ b/thumbnails/pkg/command/root.go @@ -5,11 +5,11 @@ import ( "os" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/thumbnails/pkg/config" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis-thumbnails command. diff --git a/web/pkg/command/root.go b/web/pkg/command/root.go index 4d1c6249aeb..371c3337e4a 100644 --- a/web/pkg/command/root.go +++ b/web/pkg/command/root.go @@ -5,11 +5,11 @@ import ( "os" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/web/pkg/config" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the web command. diff --git a/webdav/pkg/command/root.go b/webdav/pkg/command/root.go index 9df612006fd..224823e0196 100644 --- a/webdav/pkg/command/root.go +++ b/webdav/pkg/command/root.go @@ -5,11 +5,11 @@ import ( "os" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/webdav/pkg/config" "github.com/thejerf/suture/v4" "github.com/urfave/cli/v2" - "github.com/wkloucek/envdecode" ) // Execute is the entry point for the ocis-webdav command.