Filter env vars configuration
This commit is contained in:
parent
adc9a65ae3
commit
a918dcd5a4
19 changed files with 284 additions and 79 deletions
32
pkg/config/env/env.go
vendored
32
pkg/config/env/env.go
vendored
|
@ -2,28 +2,38 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// DefaultNamePrefix is the default prefix for environment variable names.
|
||||
const DefaultNamePrefix = "TRAEFIK_"
|
||||
|
||||
// Decode decodes the given environment variables into the given element.
|
||||
// The operation goes through four stages roughly summarized as:
|
||||
// env vars -> map
|
||||
// map -> tree of untyped nodes
|
||||
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
|
||||
// "typed" nodes -> typed element
|
||||
func Decode(environ []string, element interface{}) error {
|
||||
func Decode(environ []string, prefix string, element interface{}) error {
|
||||
if err := checkPrefix(prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vars := make(map[string]string)
|
||||
for _, evr := range environ {
|
||||
n := strings.SplitN(evr, "=", 2)
|
||||
if strings.HasPrefix(strings.ToUpper(n[0]), "TRAEFIK_") {
|
||||
if strings.HasPrefix(strings.ToUpper(n[0]), prefix) {
|
||||
key := strings.ReplaceAll(strings.ToLower(n[0]), "_", ".")
|
||||
vars[key] = n[1]
|
||||
}
|
||||
}
|
||||
|
||||
return parser.Decode(vars, element)
|
||||
rootName := strings.ToLower(prefix[:len(prefix)-1])
|
||||
return parser.Decode(vars, element, rootName)
|
||||
}
|
||||
|
||||
// Encode encodes the configuration in element into the environment variables represented in the returned Flats.
|
||||
|
@ -36,7 +46,7 @@ func Encode(element interface{}) ([]parser.Flat, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
node, err := parser.EncodeToNode(element, false)
|
||||
node, err := parser.EncodeToNode(element, parser.DefaultRootName, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -48,3 +58,17 @@ func Encode(element interface{}) ([]parser.Flat, error) {
|
|||
|
||||
return parser.EncodeToFlat(element, node, parser.FlatOpts{Case: "upper", Separator: "_"})
|
||||
}
|
||||
|
||||
func checkPrefix(prefix string) error {
|
||||
prefixPattern := `[a-zA-Z0-9]+_`
|
||||
matched, err := regexp.MatchString(prefixPattern, prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return fmt.Errorf("invalid prefix %q, the prefix pattern must match the following pattern: %s", prefix, prefixPattern)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
38
pkg/config/env/env_test.go
vendored
38
pkg/config/env/env_test.go
vendored
|
@ -173,7 +173,7 @@ func TestDecode(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := Decode(test.environ, test.element)
|
||||
err := Decode(test.environ, DefaultNamePrefix, test.element)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, test.element)
|
||||
|
@ -460,39 +460,3 @@ func TestEncode(t *testing.T) {
|
|||
|
||||
assert.Equal(t, expected, flats)
|
||||
}
|
||||
|
||||
type Ya struct {
|
||||
Foo *Yaa
|
||||
Field1 string
|
||||
Field2 bool
|
||||
Field3 int
|
||||
Field4 map[string]string
|
||||
Field5 map[string]int
|
||||
Field6 map[string]struct{ Field string }
|
||||
Field7 map[string]struct{ Field map[string]string }
|
||||
Field8 map[string]*struct{ Field string }
|
||||
Field9 map[string]*struct{ Field map[string]string }
|
||||
Field10 struct{ Field string }
|
||||
Field11 *struct{ Field string }
|
||||
Field12 *string
|
||||
Field13 *bool
|
||||
Field14 *int
|
||||
Field15 []int
|
||||
}
|
||||
|
||||
type Yaa struct {
|
||||
FieldIn1 string
|
||||
FieldIn2 bool
|
||||
FieldIn3 int
|
||||
FieldIn4 map[string]string
|
||||
FieldIn5 map[string]int
|
||||
FieldIn6 map[string]struct{ Field string }
|
||||
FieldIn7 map[string]struct{ Field map[string]string }
|
||||
FieldIn8 map[string]*struct{ Field string }
|
||||
FieldIn9 map[string]*struct{ Field map[string]string }
|
||||
FieldIn10 struct{ Field string }
|
||||
FieldIn11 *struct{ Field string }
|
||||
FieldIn12 *string
|
||||
FieldIn13 *bool
|
||||
FieldIn14 *int
|
||||
}
|
||||
|
|
64
pkg/config/env/filter.go
vendored
Normal file
64
pkg/config/env/filter.go
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// FindPrefixedEnvVars finds prefixed environment variables.
|
||||
func FindPrefixedEnvVars(environ []string, prefix string, element interface{}) []string {
|
||||
prefixes := getRootPrefixes(element, prefix)
|
||||
|
||||
var values []string
|
||||
for _, px := range prefixes {
|
||||
for _, value := range environ {
|
||||
if strings.HasPrefix(value, px) {
|
||||
values = append(values, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func getRootPrefixes(element interface{}, prefix string) []string {
|
||||
if element == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rootType := reflect.TypeOf(element)
|
||||
|
||||
return getPrefixes(prefix, rootType)
|
||||
}
|
||||
|
||||
func getPrefixes(prefix string, rootType reflect.Type) []string {
|
||||
var names []string
|
||||
|
||||
if rootType.Kind() == reflect.Ptr {
|
||||
rootType = rootType.Elem()
|
||||
}
|
||||
|
||||
if rootType.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < rootType.NumField(); i++ {
|
||||
field := rootType.Field(i)
|
||||
|
||||
if !parser.IsExported(field) {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Anonymous &&
|
||||
(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct || field.Type.Kind() == reflect.Struct) {
|
||||
names = append(names, getPrefixes(prefix, field.Type)...)
|
||||
continue
|
||||
}
|
||||
|
||||
names = append(names, prefix+strings.ToUpper(field.Name))
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
87
pkg/config/env/filter_test.go
vendored
Normal file
87
pkg/config/env/filter_test.go
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFindPrefixedEnvVars(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
environ []string
|
||||
element interface{}
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "exact name",
|
||||
environ: []string{"TRAEFIK_FOO"},
|
||||
element: &Yo{},
|
||||
expected: []string{"TRAEFIK_FOO"},
|
||||
},
|
||||
{
|
||||
desc: "prefixed name",
|
||||
environ: []string{"TRAEFIK_FII01"},
|
||||
element: &Yo{},
|
||||
expected: []string{"TRAEFIK_FII01"},
|
||||
},
|
||||
{
|
||||
desc: "excluded env vars",
|
||||
environ: []string{"TRAEFIK_NOPE", "TRAEFIK_NO"},
|
||||
element: &Yo{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "filter",
|
||||
environ: []string{"TRAEFIK_NOPE", "TRAEFIK_NO", "TRAEFIK_FOO", "TRAEFIK_FII01"},
|
||||
element: &Yo{},
|
||||
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII01"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
vars := FindPrefixedEnvVars(test.environ, DefaultNamePrefix, test.element)
|
||||
|
||||
assert.Equal(t, test.expected, vars)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getRootFieldNames(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
element interface{}
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "simple fields",
|
||||
element: &Yo{},
|
||||
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU", "TRAEFIK_YI", "TRAEFIK_YU"},
|
||||
},
|
||||
{
|
||||
desc: "embedded struct",
|
||||
element: &Yu{},
|
||||
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU"},
|
||||
},
|
||||
{
|
||||
desc: "embedded struct pointer",
|
||||
element: &Ye{},
|
||||
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
names := getRootPrefixes(test.element, DefaultNamePrefix)
|
||||
|
||||
assert.Equal(t, test.expected, names)
|
||||
})
|
||||
}
|
||||
}
|
69
pkg/config/env/fixtures_test.go
vendored
Normal file
69
pkg/config/env/fixtures_test.go
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
package env
|
||||
|
||||
type Ya struct {
|
||||
Foo *Yaa
|
||||
Field1 string
|
||||
Field2 bool
|
||||
Field3 int
|
||||
Field4 map[string]string
|
||||
Field5 map[string]int
|
||||
Field6 map[string]struct{ Field string }
|
||||
Field7 map[string]struct{ Field map[string]string }
|
||||
Field8 map[string]*struct{ Field string }
|
||||
Field9 map[string]*struct{ Field map[string]string }
|
||||
Field10 struct{ Field string }
|
||||
Field11 *struct{ Field string }
|
||||
Field12 *string
|
||||
Field13 *bool
|
||||
Field14 *int
|
||||
Field15 []int
|
||||
}
|
||||
|
||||
type Yaa struct {
|
||||
FieldIn1 string
|
||||
FieldIn2 bool
|
||||
FieldIn3 int
|
||||
FieldIn4 map[string]string
|
||||
FieldIn5 map[string]int
|
||||
FieldIn6 map[string]struct{ Field string }
|
||||
FieldIn7 map[string]struct{ Field map[string]string }
|
||||
FieldIn8 map[string]*struct{ Field string }
|
||||
FieldIn9 map[string]*struct{ Field map[string]string }
|
||||
FieldIn10 struct{ Field string }
|
||||
FieldIn11 *struct{ Field string }
|
||||
FieldIn12 *string
|
||||
FieldIn13 *bool
|
||||
FieldIn14 *int
|
||||
}
|
||||
|
||||
type Yo struct {
|
||||
Foo string `description:"Foo description"`
|
||||
Fii string `description:"Fii description"`
|
||||
Fuu string `description:"Fuu description"`
|
||||
Yi *Yi `label:"allowEmpty"`
|
||||
Yu *Yi
|
||||
}
|
||||
|
||||
func (y *Yo) SetDefaults() {
|
||||
y.Foo = "foo"
|
||||
y.Fii = "fii"
|
||||
}
|
||||
|
||||
type Yi struct {
|
||||
Foo string
|
||||
Fii string
|
||||
Fuu string
|
||||
}
|
||||
|
||||
func (y *Yi) SetDefaults() {
|
||||
y.Foo = "foo"
|
||||
y.Fii = "fii"
|
||||
}
|
||||
|
||||
type Yu struct {
|
||||
Yi
|
||||
}
|
||||
|
||||
type Ye struct {
|
||||
*Yi
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue