maintain envdecode inside ocis-pkg
This commit is contained in:
committed by
Jörn Friedrich Dreyer
parent
21ed07d90b
commit
5034e399bb
@@ -7,10 +7,10 @@ import (
|
|||||||
|
|
||||||
"github.com/owncloud/ocis/accounts/pkg/config"
|
"github.com/owncloud/ocis/accounts/pkg/config"
|
||||||
ociscfg "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/version"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis-accounts command.
|
// Execute is the entry point for the ocis-accounts command.
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
|
|
||||||
"github.com/owncloud/ocis/glauth/pkg/config"
|
"github.com/owncloud/ocis/glauth/pkg/config"
|
||||||
ociscfg "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/version"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis-glauth command.
|
// Execute is the entry point for the ocis-glauth command.
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ require (
|
|||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/thejerf/suture/v4 v4.0.1
|
github.com/thejerf/suture/v4 v4.0.1
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
github.com/wkloucek/envdecode v0.0.0-20211216135343-360f0d3c2679
|
|
||||||
go-micro.dev/v4 v4.5.0
|
go-micro.dev/v4 v4.5.0
|
||||||
go.opencensus.io v0.23.0
|
go.opencensus.io v0.23.0
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0
|
||||||
|
|||||||
@@ -1316,8 +1316,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/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 h1:KlvGyHstD1kkGZkPtHCyCfRYS0cz84uk6rrW/Dnhdtk=
|
||||||
github.com/wk8/go-ordered-map v0.2.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk=
|
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.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||||
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
||||||
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
|
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
|
|
||||||
"github.com/owncloud/ocis/graph-explorer/pkg/config"
|
"github.com/owncloud/ocis/graph-explorer/pkg/config"
|
||||||
ociscfg "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/version"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the graph-explorer command.
|
// Execute is the entry point for the graph-explorer command.
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/owncloud/ocis/ocis-pkg/config/envdecode"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
|
|
||||||
"github.com/owncloud/ocis/graph/pkg/config"
|
"github.com/owncloud/ocis/graph/pkg/config"
|
||||||
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
|
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
|
|
||||||
"github.com/owncloud/ocis/idp/pkg/config"
|
"github.com/owncloud/ocis/idp/pkg/config"
|
||||||
ociscfg "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/version"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis-idp command.
|
// Execute is the entry point for the ocis-idp command.
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,10 @@ import (
|
|||||||
|
|
||||||
"github.com/owncloud/ocis/ocis-pkg/config"
|
"github.com/owncloud/ocis/ocis-pkg/config"
|
||||||
ociscfg "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/version"
|
||||||
"github.com/owncloud/ocis/ocis/pkg/register"
|
"github.com/owncloud/ocis/ocis/pkg/register"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis command.
|
// Execute is the entry point for the ocis command.
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
ociscfg "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/version"
|
||||||
"github.com/owncloud/ocis/ocs/pkg/config"
|
"github.com/owncloud/ocis/ocs/pkg/config"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis-ocs command.
|
// Execute is the entry point for the ocis-ocs command.
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
ociscfg "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/version"
|
||||||
"github.com/owncloud/ocis/proxy/pkg/config"
|
"github.com/owncloud/ocis/proxy/pkg/config"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis-proxy command.
|
// Execute is the entry point for the ocis-proxy command.
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
ociscfg "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/version"
|
||||||
"github.com/owncloud/ocis/settings/pkg/config"
|
"github.com/owncloud/ocis/settings/pkg/config"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis-settings command.
|
// Execute is the entry point for the ocis-settings command.
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
ociscfg "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/version"
|
||||||
"github.com/owncloud/ocis/store/pkg/config"
|
"github.com/owncloud/ocis/store/pkg/config"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis-store command.
|
// Execute is the entry point for the ocis-store command.
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
ociscfg "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/version"
|
||||||
"github.com/owncloud/ocis/thumbnails/pkg/config"
|
"github.com/owncloud/ocis/thumbnails/pkg/config"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis-thumbnails command.
|
// Execute is the entry point for the ocis-thumbnails command.
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
ociscfg "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/version"
|
||||||
"github.com/owncloud/ocis/web/pkg/config"
|
"github.com/owncloud/ocis/web/pkg/config"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the web command.
|
// Execute is the entry point for the web command.
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
ociscfg "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/version"
|
||||||
"github.com/owncloud/ocis/webdav/pkg/config"
|
"github.com/owncloud/ocis/webdav/pkg/config"
|
||||||
"github.com/thejerf/suture/v4"
|
"github.com/thejerf/suture/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wkloucek/envdecode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute is the entry point for the ocis-webdav command.
|
// Execute is the entry point for the ocis-webdav command.
|
||||||
|
|||||||
Reference in New Issue
Block a user