Add option to cosume connect events rather than polling to restart deployments

This commit is contained in:
jillianwilson
2021-10-06 14:50:47 -03:00
parent b35c668959
commit a5f4a7a0c1
733 changed files with 86908 additions and 24010 deletions

8
vendor/github.com/sethvargo/go-envconfig/AUTHORS generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# This is the list of envconfig authors for copyright purposes.
#
# This does not necessarily list everyone who has contributed code, since in
# some cases, their employer may be the copyright holder. To see the full list
# of contributors, see the revision history in source control.
Google LLC
Seth Vargo

202
vendor/github.com/sethvargo/go-envconfig/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

59
vendor/github.com/sethvargo/go-envconfig/Makefile generated vendored Normal file
View File

@@ -0,0 +1,59 @@
# Copyright The envconfig Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
VETTERS = "asmdecl,assign,atomic,bools,buildtag,cgocall,composites,copylocks,errorsas,httpresponse,loopclosure,lostcancel,nilfunc,printf,shift,stdmethods,structtag,tests,unmarshal,unreachable,unsafeptr,unusedresult"
GOFMT_FILES = $(shell go list -f '{{.Dir}}' ./...)
fmtcheck:
@command -v goimports > /dev/null 2>&1 || (cd tools && go get golang.org/x/tools/cmd/goimports && cd ..)
@CHANGES="$$(goimports -d $(GOFMT_FILES))"; \
if [ -n "$${CHANGES}" ]; then \
echo "Unformatted (run goimports -w .):\n\n$${CHANGES}\n\n"; \
exit 1; \
fi
@# Annoyingly, goimports does not support the simplify flag.
@CHANGES="$$(gofmt -s -d $(GOFMT_FILES))"; \
if [ -n "$${CHANGES}" ]; then \
echo "Unformatted (run gofmt -s -w .):\n\n$${CHANGES}\n\n"; \
exit 1; \
fi
.PHONY: fmtcheck
spellcheck:
@command -v misspell > /dev/null 2>&1 || (cd tools && go get github.com/client9/misspell/cmd/misspell && cd ..)
@misspell -locale="US" -error -source="text" **/*
.PHONY: spellcheck
staticcheck:
@command -v staticcheck > /dev/null 2>&1 || (cd tools && go get honnef.co/go/tools/cmd/staticcheck && cd ..)
@staticcheck -checks="all" -tests $(GOFMT_FILES)
.PHONY: staticcheck
test:
@go test \
-count=1 \
-short \
-timeout=5m \
-vet="${VETTERS}" \
./...
.PHONY: test
test-acc:
@go test \
-count=1 \
-race \
-timeout=10m \
-vet="${VETTERS}" \
./...
.PHONY: test-acc

303
vendor/github.com/sethvargo/go-envconfig/README.md generated vendored Normal file
View File

@@ -0,0 +1,303 @@
# Envconfig
[![GoDoc](https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/mod/github.com/sethvargo/go-envconfig)
[![GitHub Actions](https://img.shields.io/github/workflow/status/sethvargo/go-envconfig/Test?style=flat-square)](https://github.com/sethvargo/go-envconfig/actions?query=workflow%3ATest)
Envconfig populates struct field values based on environment variables or
arbitrary lookup functions. It supports pre-setting mutations, which is useful
for things like converting values to uppercase, trimming whitespace, or looking
up secrets.
**Note:** Versions prior to v0.2 used a different import path. This README and
examples are for v0.2+.
## Usage
Define a struct with fields using the `env` tag:
```go
type MyConfig struct {
Port int `env:"PORT"`
Username string `env:"USERNAME"`
}
```
Set some environment variables:
```sh
export PORT=5555
export USERNAME=yoyo
```
Process it using envconfig:
```go
package main
import (
"context"
"log"
"github.com/sethvargo/go-envconfig"
)
func main() {
ctx := context.Background()
var c MyConfig
if err := envconfig.Process(ctx, &c); err != nil {
log.Fatal(err)
}
// c.Port = 5555
// c.Username = "yoyo"
}
```
You can also use nested structs, just remember that any fields you want to
process must be public:
```go
type MyConfig struct {
Database *DatabaseConfig
}
type DatabaseConfig struct {
Port int `env:"PORT"`
Username string `env:"USERNAME"`
}
```
## Configuration
Use the `env` struct tag to define configuration.
### Required
If a field is required, processing will error if the environment variable is
unset.
```go
type MyStruct struct {
Port int `env:"PORT,required"`
}
```
It is invalid to have a field as both `required` and `default`.
### Default
If an environment variable is not set, the field will be set to the default
value. Note that the environment variable must not be set (e.g. `unset PORT`).
If the environment variable is the empty string, that counts as a "value" and
the default will not be used.
```go
type MyStruct struct {
Port int `env:"PORT,default=5555"`
}
```
You can also set the default value to another field or value from the
environment, for example:
```go
type MyStruct struct {
DefaultPort int `env:"DEFAULT_PORT,default=5555"`
Port int `env:"OVERRIDE_PORT,default=$DEFAULT_PORT"`
}
```
The value for `Port` will default to the value of `DEFAULT_PORT`.
It is invalid to have a field as both `required` and `default`.
### Prefix
For shared, embedded structs, you can define a prefix to use when processing
struct values for that embed.
```go
type SharedConfig struct {
Port int `env:"PORT,default=5555"`
}
type Server1 struct {
// This processes Port from $FOO_PORT.
*SharedConfig `env:",prefix=FOO_"`
}
type Server2 struct {
// This processes Port from $BAR_PORT.
*SharedConfig `env:",prefix=BAR_"`
}
```
It is invalid to specify a prefix on non-struct fields.
## Complex Types
### Durations
In the environment, `time.Duration` values are specified as a parsable Go
duration:
```go
type MyStruct struct {
MyVar time.Duration `env:"MYVAR"`
}
```
```bash
export MYVAR="10m" # 10 * time.Minute
```
### TextUnmarshaler / BinaryUnmarshaler
Types that implement `TextUnmarshaler` or `BinaryUnmarshaler` are processed as such.
### json.Unmarshaler
Types that implement `json.Unmarshaler` are processed as such.
### gob.Decoder
Types that implement `gob.Decoder` are processed as such.
### Slices
Slices are specified as comma-separated values:
```go
type MyStruct struct {
MyVar []string `env:"MYVAR"`
}
```
```bash
export MYVAR="a,b,c,d" # []string{"a", "b", "c", "d"}
```
Note that byte slices are special cased and interpreted as strings from the
environment.
### Maps
Maps are specified as comma-separated key:value pairs:
```go
type MyStruct struct {
MyVar map[string]string `env:"MYVAR"`
}
```
```bash
export MYVAR="a:b,c:d" # map[string]string{"a":"b", "c":"d"}
```
### Structs
Envconfig walks the entire struct, so deeply-nested fields are also supported. You can also define your own decoder (see below).
## Prefixing
You can define a custom prefix using the `PrefixLookuper`. This will lookup
values in the environment by prefixing the keys with the provided value:
```go
type MyStruct struct {
MyVar string `env:"MYVAR"`
}
```
```go
// Process variables, but look for the "APP_" prefix.
l := envconfig.PrefixLookuper("APP_", envconfig.OsLookuper())
if err := envconfig.ProcessWith(ctx, &c, l); err != nil {
panic(err)
}
```
```bash
export APP_MYVAR="foo"
```
## Extension
All built-in types are supported except Func and Chan. If you need to define a
custom decoder, implement `Decoder`:
```go
type MyStruct struct {
field string
}
func (v *MyStruct) EnvDecode(val string) error {
v.field = fmt.Sprintf("PREFIX-%s", val)
return nil
}
```
If you need to modify environment variable values before processing, you can
specify a custom `Mutator`:
```go
type Config struct {
Password `env:"PASSWORD"`
}
func resolveSecretFunc(ctx context.Context, key, value string) (string, error) {
if strings.HasPrefix(key, "secret://") {
return secretmanager.Resolve(ctx, value) // example
}
return value, nil
}
var config Config
envconfig.ProcessWith(ctx, &config, envconfig.OsLookuper(), resolveSecretFunc)
```
## Testing
Relying on the environment in tests can be troublesome because environment
variables are global, which makes it difficult to parallelize the tests.
Envconfig supports extracting data from anything that returns a value:
```go
lookuper := envconfig.MapLookuper(map[string]string{
"FOO": "bar",
"ZIP": "zap",
})
var config Config
envconfig.ProcessWith(ctx, &config, lookuper)
```
Now you can parallelize all your tests by providing a map for the lookup
function. In fact, that's how the tests in this repo work, so check there for an
example.
You can also combine multiple lookupers with `MultiLookuper`. See the GoDoc for
more information and examples.
## Inspiration
This library is conceptually similar to [kelseyhightower/envconfig](https://github.com/kelseyhightower/envconfig), with the following
major behavioral differences:
- Adds support for specifying a custom lookup function (such as a map), which
is useful for testing.
- Only populates fields if they contain zero or nil values. This means you can
pre-initialize a struct and any pre-populated fields will not be overwritten
during processing.
- Support for interpolation. The default value for a field can be the value of
another field.
- Support for arbitrary mutators that change/resolve data before type
conversion.

57
vendor/github.com/sethvargo/go-envconfig/decoding.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
// Copyright The envconfig Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package envconfig
import (
"encoding/base64"
"encoding/hex"
"strings"
)
// Base64Bytes is a slice of bytes where the information is base64-encoded in
// the environment variable.
type Base64Bytes []byte
// EnvDecode implements env.Decoder.
func (b *Base64Bytes) EnvDecode(val string) error {
val = strings.ReplaceAll(val, "+", "-")
val = strings.ReplaceAll(val, "/", "_")
val = strings.TrimRight(val, "=")
var err error
*b, err = base64.RawURLEncoding.DecodeString(val)
return err
}
// Bytes returns the underlying bytes.
func (b Base64Bytes) Bytes() []byte {
return []byte(b)
}
// HexBytes is a slice of bytes where the information is hex-encoded in the
// environment variable.
type HexBytes []byte
// EnvDecode implements env.Decoder.
func (b *HexBytes) EnvDecode(val string) error {
var err error
*b, err = hex.DecodeString(val)
return err
}
// Bytes returns the underlying bytes.
func (b HexBytes) Bytes() []byte {
return []byte(b)
}

592
vendor/github.com/sethvargo/go-envconfig/envconfig.go generated vendored Normal file
View File

@@ -0,0 +1,592 @@
// Copyright The envconfig Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package envconfig populates struct fields based on environment variable
// values (or anything that responds to "Lookup"). Structs declare their
// environment dependencies using the `env` tag with the key being the name of
// the environment variable, case sensitive.
//
// type MyStruct struct {
// A string `env:"A"` // resolves A to $A
// B string `env:"B,required"` // resolves B to $B, errors if $B is unset
// C string `env:"C,default=foo"` // resolves C to $C, defaults to "foo"
//
// D string `env:"D,required,default=foo"` // error, cannot be required and default
// E string `env:""` // error, must specify key
// }
//
// All built-in types are supported except Func and Chan. If you need to define
// a custom decoder, implement Decoder:
//
// type MyStruct struct {
// field string
// }
//
// func (v *MyStruct) EnvDecode(val string) error {
// v.field = fmt.Sprintf("PREFIX-%s", val)
// return nil
// }
//
// In the environment, slices are specified as comma-separated values:
//
// export MYVAR="a,b,c,d" // []string{"a", "b", "c", "d"}
//
// In the environment, maps are specified as comma-separated key:value pairs:
//
// export MYVAR="a:b,c:d" // map[string]string{"a":"b", "c":"d"}
//
// If you need to modify environment variable values before processing, you can
// specify a custom mutator:
//
// type Config struct {
// Password `env:"PASSWORD_SECRET"`
// }
//
// func resolveSecretFunc(ctx context.Context, key, value string) (string, error) {
// if strings.HasPrefix(key, "secret://") {
// return secretmanager.Resolve(ctx, value) // example
// }
// return value, nil
// }
//
// var config Config
// ProcessWith(&config, OsLookuper(), resolveSecretFunc)
//
package envconfig
import (
"context"
"encoding"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"time"
)
const (
envTag = "env"
optRequired = "required"
optDefault = "default="
optPrefix = "prefix="
)
// Error is a custom error type for errors returned by envconfig.
type Error string
// Error implements error.
func (e Error) Error() string {
return string(e)
}
const (
ErrInvalidMapItem = Error("invalid map item")
ErrLookuperNil = Error("lookuper cannot be nil")
ErrMissingKey = Error("missing key")
ErrMissingRequired = Error("missing required value")
ErrNotPtr = Error("input must be a pointer")
ErrNotStruct = Error("input must be a struct")
ErrPrefixNotStruct = Error("prefix is only valid on struct types")
ErrPrivateField = Error("cannot parse private fields")
ErrRequiredAndDefault = Error("field cannot be required and have a default value")
ErrUnknownOption = Error("unknown option")
)
// Lookuper is an interface that provides a lookup for a string-based key.
type Lookuper interface {
// Lookup searches for the given key and returns the corresponding string
// value. If a value is found, it returns the value and true. If a value is
// not found, it returns the empty string and false.
Lookup(key string) (string, bool)
}
// osLookuper looks up environment configuration from the local environment.
type osLookuper struct{}
// Verify implements interface.
var _ Lookuper = (*osLookuper)(nil)
func (o *osLookuper) Lookup(key string) (string, bool) {
return os.LookupEnv(key)
}
// OsLookuper returns a lookuper that uses the environment (os.LookupEnv) to
// resolve values.
func OsLookuper() Lookuper {
return new(osLookuper)
}
type mapLookuper map[string]string
var _ Lookuper = (*mapLookuper)(nil)
func (m mapLookuper) Lookup(key string) (string, bool) {
v, ok := m[key]
return v, ok
}
// MapLookuper looks up environment configuration from a provided map. This is
// useful for testing, especially in parallel, since it does not require you to
// mutate the parent environment (which is stateful).
func MapLookuper(m map[string]string) Lookuper {
return mapLookuper(m)
}
type multiLookuper struct {
ls []Lookuper
}
var _ Lookuper = (*multiLookuper)(nil)
func (m *multiLookuper) Lookup(key string) (string, bool) {
for _, l := range m.ls {
if v, ok := l.Lookup(key); ok {
return v, true
}
}
return "", false
}
// PrefixLookuper looks up environment configuration using the specified prefix.
// This is useful if you want all your variables to start with a particular
// prefix like "MY_APP_".
func PrefixLookuper(prefix string, l Lookuper) Lookuper {
if typ, ok := l.(*prefixLookuper); ok {
return &prefixLookuper{prefix: typ.prefix + prefix, l: typ.l}
}
return &prefixLookuper{prefix: prefix, l: l}
}
type prefixLookuper struct {
prefix string
l Lookuper
}
func (p *prefixLookuper) Lookup(key string) (string, bool) {
return p.l.Lookup(p.prefix + key)
}
// MultiLookuper wraps a collection of lookupers. It does not combine them, and
// lookups appear in the order in which they are provided to the initializer.
func MultiLookuper(lookupers ...Lookuper) Lookuper {
return &multiLookuper{ls: lookupers}
}
// Decoder is an interface that custom types/fields can implement to control how
// decoding takes place. For example:
//
// type MyType string
//
// func (mt MyType) EnvDecode(val string) error {
// return "CUSTOM-"+val
// }
//
type Decoder interface {
EnvDecode(val string) error
}
// MutatorFunc is a function that mutates a given value before it is passed
// along for processing. This is useful if you want to mutate the environment
// variable value before it's converted to the proper type.
type MutatorFunc func(ctx context.Context, k, v string) (string, error)
// options are internal options for decoding.
type options struct {
Default string
Required bool
Prefix string
}
// Process processes the struct using the environment. See ProcessWith for a
// more customizable version.
func Process(ctx context.Context, i interface{}) error {
return ProcessWith(ctx, i, OsLookuper())
}
// ProcessWith processes the given interface with the given lookuper. See the
// package-level documentation for specific examples and behaviors.
func ProcessWith(ctx context.Context, i interface{}, l Lookuper, fns ...MutatorFunc) error {
if l == nil {
return ErrLookuperNil
}
v := reflect.ValueOf(i)
if v.Kind() != reflect.Ptr {
return ErrNotPtr
}
e := v.Elem()
if e.Kind() != reflect.Struct {
return ErrNotStruct
}
t := e.Type()
for i := 0; i < t.NumField(); i++ {
ef := e.Field(i)
tf := t.Field(i)
tag := tf.Tag.Get(envTag)
if !ef.CanSet() {
if tag != "" {
// There's an "env" tag on a private field, we can't alter it, and it's
// likely a mistake. Return an error so the user can handle.
return fmt.Errorf("%s: %w", tf.Name, ErrPrivateField)
}
// Otherwise continue to the next field.
continue
}
// Parse the key and options.
key, opts, err := keyAndOpts(tag)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
// Initialize pointer structs.
for ef.Kind() == reflect.Ptr {
if ef.IsNil() {
if ef.Type().Elem().Kind() != reflect.Struct {
// This is a nil pointer to something that isn't a struct, like
// *string. Move along.
break
}
// Nil pointer to a struct, create so we can traverse.
ef.Set(reflect.New(ef.Type().Elem()))
}
ef = ef.Elem()
}
// Special case handle structs. This has to come after the value resolution in
// case the struct has a custom decoder.
if ef.Kind() == reflect.Struct {
for ef.CanAddr() {
ef = ef.Addr()
}
// Lookup the value, ignoring an error if the key isn't defined. This is
// required for nested structs that don't declare their own `env` keys,
// but have internal fields with an `env` defined.
val, err := lookup(key, opts, l)
if err != nil && !errors.Is(err, ErrMissingKey) {
return fmt.Errorf("%s: %w", tf.Name, err)
}
if ok, err := processAsDecoder(val, ef); ok {
if err != nil {
return err
}
continue
}
plu := l
if opts.Prefix != "" {
plu = PrefixLookuper(opts.Prefix, l)
}
if err := ProcessWith(ctx, ef.Interface(), plu, fns...); err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
continue
}
// It's invalid to have a prefix on a non-struct field.
if opts.Prefix != "" {
return ErrPrefixNotStruct
}
// Stop processing if there's no env tag (this comes after nested parsing),
// in case there's an env tag in an embedded struct.
if tag == "" {
continue
}
// The field already has a non-zero value, do not overwrite.
if !ef.IsZero() {
continue
}
val, err := lookup(key, opts, l)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
// Apply any mutators. Mutators are applied after the lookup, but before any
// type conversions. They always resolve to a string (or error)
for _, fn := range fns {
if fn != nil {
val, err = fn(ctx, key, val)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
}
}
// Set value.
if err := processField(val, ef); err != nil {
return fmt.Errorf("%s(%q): %w", tf.Name, val, err)
}
}
return nil
}
// keyAndOpts parses the given tag value (e.g. env:"foo,required") and
// returns the key name and options as a list.
func keyAndOpts(tag string) (string, *options, error) {
parts := strings.Split(tag, ",")
key, tagOpts := strings.TrimSpace(parts[0]), parts[1:]
var opts options
LOOP:
for i, o := range tagOpts {
o = strings.TrimSpace(o)
switch {
case o == optRequired:
opts.Required = true
case strings.HasPrefix(o, optPrefix):
opts.Prefix = strings.TrimPrefix(o, optPrefix)
case strings.HasPrefix(o, optDefault):
// If a default value was given, assume everything after is the provided
// value, including comma-seprated items.
o = strings.TrimLeft(strings.Join(tagOpts[i:], ","), " ")
opts.Default = strings.TrimPrefix(o, optDefault)
break LOOP
default:
return "", nil, fmt.Errorf("%q: %w", o, ErrUnknownOption)
}
}
return key, &opts, nil
}
// lookup looks up the given key using the provided Lookuper and options.
func lookup(key string, opts *options, l Lookuper) (string, error) {
if key == "" {
// The struct has something like `env:",required"`, which is likely a
// mistake. We could try to infer the envvar from the field name, but that
// feels too magical.
return "", ErrMissingKey
}
if opts.Required && opts.Default != "" {
// Having a default value on a required value doesn't make sense.
return "", ErrRequiredAndDefault
}
// Lookup value.
val, ok := l.Lookup(key)
if !ok {
if opts.Required {
return "", fmt.Errorf("%w: %s", ErrMissingRequired, key)
}
if opts.Default != "" {
// Expand the default value. This allows for a default value that maps to
// a different variable.
val = os.Expand(opts.Default, func(i string) string {
s, ok := l.Lookup(i)
if ok {
return s
}
return ""
})
}
}
return val, nil
}
// processAsDecoder processes the given value as a decoder or custom
// unmarshaller.
func processAsDecoder(v string, ef reflect.Value) (bool, error) {
// Keep a running error. It's possible that a property might implement
// multiple decoders, and we don't know *which* decoder will succeed. If we
// get through all of them, we'll return the most recent error.
var imp bool
var err error
// Resolve any pointers.
for ef.CanAddr() {
ef = ef.Addr()
}
if ef.CanInterface() {
iface := ef.Interface()
if dec, ok := iface.(Decoder); ok {
imp = true
if err = dec.EnvDecode(v); err == nil {
return true, nil
}
}
if tu, ok := iface.(encoding.BinaryUnmarshaler); ok {
imp = true
if err = tu.UnmarshalBinary([]byte(v)); err == nil {
return true, nil
}
}
if tu, ok := iface.(gob.GobDecoder); ok {
imp = true
if err = tu.GobDecode([]byte(v)); err == nil {
return true, nil
}
}
if tu, ok := iface.(json.Unmarshaler); ok {
imp = true
if err = tu.UnmarshalJSON([]byte(v)); err == nil {
return true, nil
}
}
if tu, ok := iface.(encoding.TextUnmarshaler); ok {
imp = true
if err = tu.UnmarshalText([]byte(v)); err == nil {
return true, nil
}
}
}
return imp, err
}
func processField(v string, ef reflect.Value) error {
// Handle pointers and uninitialized pointers.
for ef.Type().Kind() == reflect.Ptr {
if ef.IsNil() {
ef.Set(reflect.New(ef.Type().Elem()))
}
ef = ef.Elem()
}
tf := ef.Type()
tk := tf.Kind()
// Handle existing decoders.
if ok, err := processAsDecoder(v, ef); ok {
return err
}
// We don't check if the value is empty earlier, because the user might want
// to define a custom decoder and treat the empty variable as a special case.
// However, if we got this far, none of the remaining parsers will succeed, so
// bail out now.
if v == "" {
return nil
}
switch tk {
case reflect.Bool:
b, err := strconv.ParseBool(v)
if err != nil {
return err
}
ef.SetBool(b)
case reflect.Float32, reflect.Float64:
f, err := strconv.ParseFloat(v, tf.Bits())
if err != nil {
return err
}
ef.SetFloat(f)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
i, err := strconv.ParseInt(v, 0, tf.Bits())
if err != nil {
return err
}
ef.SetInt(i)
case reflect.Int64:
// Special case time.Duration values.
if tf.PkgPath() == "time" && tf.Name() == "Duration" {
d, err := time.ParseDuration(v)
if err != nil {
return err
}
ef.SetInt(int64(d))
} else {
i, err := strconv.ParseInt(v, 0, tf.Bits())
if err != nil {
return err
}
ef.SetInt(i)
}
case reflect.String:
ef.SetString(v)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
i, err := strconv.ParseUint(v, 0, tf.Bits())
if err != nil {
return err
}
ef.SetUint(i)
case reflect.Interface:
return fmt.Errorf("cannot decode into interfaces")
// Maps
case reflect.Map:
vals := strings.Split(v, ",")
mp := reflect.MakeMapWithSize(tf, len(vals))
for _, val := range vals {
pair := strings.SplitN(val, ":", 2)
if len(pair) < 2 {
return fmt.Errorf("%s: %w", val, ErrInvalidMapItem)
}
mKey, mVal := strings.TrimSpace(pair[0]), strings.TrimSpace(pair[1])
k := reflect.New(tf.Key()).Elem()
if err := processField(mKey, k); err != nil {
return fmt.Errorf("%s: %w", mKey, err)
}
v := reflect.New(tf.Elem()).Elem()
if err := processField(mVal, v); err != nil {
return fmt.Errorf("%s: %w", mVal, err)
}
mp.SetMapIndex(k, v)
}
ef.Set(mp)
// Slices
case reflect.Slice:
// Special case: []byte
if tf.Elem().Kind() == reflect.Uint8 {
ef.Set(reflect.ValueOf([]byte(v)))
} else {
vals := strings.Split(v, ",")
s := reflect.MakeSlice(tf, len(vals), len(vals))
for i, val := range vals {
val = strings.TrimSpace(val)
if err := processField(val, s.Index(i)); err != nil {
return fmt.Errorf("%s: %w", val, err)
}
}
ef.Set(s)
}
}
return nil
}

5
vendor/github.com/sethvargo/go-envconfig/go.mod generated vendored Normal file
View File

@@ -0,0 +1,5 @@
module github.com/sethvargo/go-envconfig
go 1.14
require github.com/google/go-cmp v0.4.1

4
vendor/github.com/sethvargo/go-envconfig/go.sum generated vendored Normal file
View File

@@ -0,0 +1,4 @@
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=