1
0
Fork 0

Adds Docker provider support

Co-authored-by: Julien Salleyron <julien@containo.us>
This commit is contained in:
Ludovic Fernandez 2019-01-18 15:18:04 +01:00 committed by Traefiker Bot
parent 8735263930
commit b54c956c5e
78 changed files with 3476 additions and 5587 deletions

View file

@ -5,8 +5,15 @@ import (
"reflect"
"strconv"
"strings"
"time"
"github.com/containous/flaeg/parse"
)
type initializer interface {
SetDefaults()
}
// Fill the fields of the element.
// nodes -> element
func Fill(element interface{}, node *Node) error {
@ -79,6 +86,13 @@ func fill(field reflect.Value, node *Node) error {
func setPtr(field reflect.Value, node *Node) error {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
if field.Type().Implements(reflect.TypeOf((*initializer)(nil)).Elem()) {
method := field.MethodByName("SetDefaults")
if method.IsValid() {
method.Call([]reflect.Value{})
}
}
}
return fill(field.Elem(), node)
@ -202,12 +216,15 @@ func setSliceAsStruct(field reflect.Value, node *Node) error {
return fmt.Errorf("invalid slice: node %s", node.Name)
}
elem := reflect.New(field.Type().Elem()).Elem()
err := setStruct(elem, node)
// use Ptr to allow "SetDefaults"
value := reflect.New(reflect.PtrTo(field.Type().Elem()))
err := setPtr(value, node)
if err != nil {
return err
}
elem := value.Elem().Elem()
field.Set(reflect.MakeSlice(field.Type(), 1, 1))
field.Index(0).Set(elem)
@ -236,12 +253,35 @@ func setMap(field reflect.Value, node *Node) error {
}
func setInt(field reflect.Value, value string, bitSize int) error {
switch field.Type() {
case reflect.TypeOf(parse.Duration(0)):
return setDuration(field, value, bitSize, time.Second)
case reflect.TypeOf(time.Duration(0)):
return setDuration(field, value, bitSize, time.Nanosecond)
default:
val, err := strconv.ParseInt(value, 10, bitSize)
if err != nil {
return err
}
field.Set(reflect.ValueOf(val).Convert(field.Type()))
return nil
}
}
func setDuration(field reflect.Value, value string, bitSize int, defaultUnit time.Duration) error {
val, err := strconv.ParseInt(value, 10, bitSize)
if err == nil {
field.Set(reflect.ValueOf(time.Duration(val) * defaultUnit).Convert(field.Type()))
return nil
}
duration, err := time.ParseDuration(value)
if err != nil {
return err
}
field.Set(reflect.ValueOf(val).Convert(field.Type()))
field.Set(reflect.ValueOf(duration).Convert(field.Type()))
return nil
}

View file

@ -3,7 +3,9 @@ package internal
import (
"reflect"
"testing"
"time"
"github.com/containous/flaeg/parse"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -363,6 +365,54 @@ func TestFill(t *testing.T) {
element: &struct{ Foo uint64 }{},
expected: expected{error: true},
},
{
desc: "time.Duration with unit",
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "4s", Kind: reflect.Int64},
},
},
element: &struct{ Foo time.Duration }{},
expected: expected{element: &struct{ Foo time.Duration }{Foo: 4 * time.Second}},
},
{
desc: "time.Duration without unit",
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "4", Kind: reflect.Int64},
},
},
element: &struct{ Foo time.Duration }{},
expected: expected{element: &struct{ Foo time.Duration }{Foo: 4 * time.Nanosecond}},
},
{
desc: "parse.Duration with unit",
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "4s", Kind: reflect.Int64},
},
},
element: &struct{ Foo parse.Duration }{},
expected: expected{element: &struct{ Foo parse.Duration }{Foo: parse.Duration(4 * time.Second)}},
},
{
desc: "parse.Duration without unit",
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "4", Kind: reflect.Int64},
},
},
element: &struct{ Foo parse.Duration }{},
expected: expected{element: &struct{ Foo parse.Duration }{Foo: parse.Duration(4 * time.Second)}},
},
{
desc: "bool",
node: &Node{
@ -1069,6 +1119,57 @@ func TestFill(t *testing.T) {
}{},
expected: expected{error: true},
},
{
desc: "pointer SetDefaults method",
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Foo",
FieldName: "Foo",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Fuu", FieldName: "Fuu", Value: "huu", Kind: reflect.String},
}},
}},
element: &struct {
Foo *initialledFoo
}{},
expected: expected{element: &struct {
Foo *initialledFoo
}{
Foo: &initialledFoo{
Fii: "default",
Fuu: "huu",
},
}},
},
{
desc: "pointer wrong SetDefaults method",
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Foo",
FieldName: "Foo",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Fuu", FieldName: "Fuu", Value: "huu", Kind: reflect.String},
}},
}},
element: &struct {
Foo *wrongInitialledFoo
}{},
expected: expected{element: &struct {
Foo *wrongInitialledFoo
}{
Foo: &wrongInitialledFoo{
Fuu: "huu",
},
}},
},
}
for _, test := range testCases {
@ -1086,3 +1187,22 @@ func TestFill(t *testing.T) {
})
}
}
type initialledFoo struct {
Fii string
Fuu string
}
func (t *initialledFoo) SetDefaults() {
t.Fii = "default"
}
type wrongInitialledFoo struct {
Fii string
Fuu string
}
func (t *wrongInitialledFoo) SetDefaults() error {
t.Fii = "default"
return nil
}

View file

@ -8,10 +8,20 @@ import (
// DecodeToNode Converts the labels to a node.
// labels -> nodes
func DecodeToNode(labels map[string]string) (*Node, error) {
func DecodeToNode(labels map[string]string, filters ...string) (*Node, error) {
var sortedKeys []string
for key := range labels {
sortedKeys = append(sortedKeys, key)
if len(filters) == 0 {
sortedKeys = append(sortedKeys, key)
continue
}
for _, filter := range filters {
if len(key) >= len(filter) && strings.EqualFold(key[:len(filter)], filter) {
sortedKeys = append(sortedKeys, key)
continue
}
}
}
sort.Strings(sortedKeys)

View file

@ -18,15 +18,13 @@ func TestDecodeToNode(t *testing.T) {
testCases := []struct {
desc string
in map[string]string
filters []string
expected expected
}{
{
desc: "level 0",
in: map[string]string{"traefik": "bar"},
expected: expected{node: &Node{
Name: "traefik",
Value: "bar",
}},
desc: "no label",
in: map[string]string{},
expected: expected{node: nil},
},
{
desc: "level 1",
@ -75,6 +73,20 @@ func TestDecodeToNode(t *testing.T) {
},
expected: expected{error: true},
},
{
desc: "several entries, prefix filter",
in: map[string]string{
"traefik.foo": "bar",
"traefik.fii": "bir",
},
filters: []string{"traefik.Foo"},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Value: "bar"},
},
}},
},
{
desc: "several entries, level 1",
in: map[string]string{
@ -172,7 +184,7 @@ func TestDecodeToNode(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
out, err := DecodeToNode(test.in)
out, err := DecodeToNode(test.in, test.filters...)
if test.expected.error {
require.Error(t, err)

View file

@ -10,6 +10,10 @@ import (
// AddMetadata Adds metadata to a node.
// nodes + element -> nodes
func AddMetadata(structure interface{}, node *Node) error {
if node == nil {
return nil
}
if len(node.Children) == 0 {
return fmt.Errorf("invalid node %s: no child", node.Name)
}

View file

@ -24,6 +24,12 @@ func TestAddMetadata(t *testing.T) {
structure interface{}
expected expected
}{
{
desc: "Node Nil",
tree: nil,
structure: nil,
expected: expected{node: nil},
},
{
desc: "Empty Node",
tree: &Node{},

View file

@ -5,21 +5,11 @@ import (
"github.com/containous/traefik/provider/label/internal"
)
// Decode Converts the labels to a configuration.
// labels -> [ node -> node + metadata (type) ] -> element (node)
func Decode(labels map[string]string) (*config.Configuration, error) {
node, err := internal.DecodeToNode(labels)
if err != nil {
return nil, err
}
// DecodeConfiguration Converts the labels to a configuration.
func DecodeConfiguration(labels map[string]string) (*config.Configuration, error) {
conf := &config.Configuration{}
err = internal.AddMetadata(conf, node)
if err != nil {
return nil, err
}
err = internal.Fill(conf, node)
err := Decode(labels, conf, "traefik.services", "traefik.routers", "traefik.middlewares")
if err != nil {
return nil, err
}
@ -27,10 +17,36 @@ func Decode(labels map[string]string) (*config.Configuration, error) {
return conf, nil
}
// Encode Converts a configuration to labels.
// EncodeConfiguration Converts a configuration to labels.
func EncodeConfiguration(conf *config.Configuration) (map[string]string, error) {
return Encode(conf)
}
// Decode Converts the labels to an element.
// labels -> [ node -> node + metadata (type) ] -> element (node)
func Decode(labels map[string]string, element interface{}, filters ...string) error {
node, err := internal.DecodeToNode(labels, filters...)
if err != nil {
return err
}
err = internal.AddMetadata(element, node)
if err != nil {
return err
}
err = internal.Fill(element, node)
if err != nil {
return err
}
return nil
}
// Encode Converts an element to labels.
// element -> node (value) -> label (node)
func Encode(conf *config.Configuration) (map[string]string, error) {
node, err := internal.EncodeToNode(conf)
func Encode(element interface{}) (map[string]string, error) {
node, err := internal.EncodeToNode(element)
if err != nil {
return nil, err
}

View file

@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestDecode(t *testing.T) {
func TestDecodeConfiguration(t *testing.T) {
labels := map[string]string{
"traefik.middlewares.Middleware0.addprefix.prefix": "foobar",
"traefik.middlewares.Middleware1.basicauth.headerfield": "foobar",
@ -123,7 +123,8 @@ func TestDecode(t *testing.T) {
"traefik.services.Service0.loadbalancer.method": "foobar",
"traefik.services.Service0.loadbalancer.passhostheader": "true",
"traefik.services.Service0.loadbalancer.responseforwarding.flushinterval": "foobar",
"traefik.services.Service0.loadbalancer.server.url": "foobar",
"traefik.services.Service0.loadbalancer.server.scheme": "foobar",
"traefik.services.Service0.loadbalancer.server.port": "8080",
"traefik.services.Service0.loadbalancer.server.weight": "42",
"traefik.services.Service0.loadbalancer.stickiness.cookiename": "foobar",
"traefik.services.Service1.loadbalancer.healthcheck.headers.name0": "foobar",
@ -137,13 +138,13 @@ func TestDecode(t *testing.T) {
"traefik.services.Service1.loadbalancer.method": "foobar",
"traefik.services.Service1.loadbalancer.passhostheader": "true",
"traefik.services.Service1.loadbalancer.responseforwarding.flushinterval": "foobar",
"traefik.services.Service1.loadbalancer.server.url": "foobar",
"traefik.services.Service1.loadbalancer.server.weight": "42",
"traefik.services.Service1.loadbalancer.server.scheme": "foobar",
"traefik.services.Service1.loadbalancer.server.port": "8080",
"traefik.services.Service1.loadbalancer.stickiness": "false",
"traefik.services.Service1.loadbalancer.stickiness.cookiename": "fui",
}
configuration, err := Decode(labels)
configuration, err := DecodeConfiguration(labels)
require.NoError(t, err)
expected := &config.Configuration{
@ -222,12 +223,12 @@ func TestDecode(t *testing.T) {
RateLimit: &config.RateLimit{
RateSet: map[string]*config.Rate{
"Rate0": {
Period: parse.Duration(42 * time.Nanosecond),
Period: parse.Duration(42 * time.Second),
Average: 42,
Burst: 42,
},
"Rate1": {
Period: parse.Duration(42 * time.Nanosecond),
Period: parse.Duration(42 * time.Second),
Average: 42,
Burst: 42,
},
@ -403,7 +404,8 @@ func TestDecode(t *testing.T) {
},
Servers: []config.Server{
{
URL: "foobar",
Scheme: "foobar",
Port: "8080",
Weight: 42,
},
},
@ -430,8 +432,9 @@ func TestDecode(t *testing.T) {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "foobar",
Weight: 42,
Scheme: "foobar",
Port: "8080",
Weight: 1,
},
},
Method: "foobar",
@ -459,7 +462,7 @@ func TestDecode(t *testing.T) {
assert.Equal(t, expected, configuration)
}
func TestEncode(t *testing.T) {
func TestEncodeConfiguration(t *testing.T) {
configuration := &config.Configuration{
Routers: map[string]*config.Router{
"Router0": {
@ -717,7 +720,8 @@ func TestEncode(t *testing.T) {
},
Servers: []config.Server{
{
URL: "foobar",
Scheme: "foobar",
Port: "8080",
Weight: 42,
},
},
@ -744,7 +748,8 @@ func TestEncode(t *testing.T) {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "foobar",
Scheme: "foobar",
Port: "8080",
Weight: 42,
},
},
@ -770,7 +775,7 @@ func TestEncode(t *testing.T) {
},
}
labels, err := Encode(configuration)
labels, err := EncodeConfiguration(configuration)
require.NoError(t, err)
expected := map[string]string{
@ -873,7 +878,6 @@ func TestEncode(t *testing.T) {
"traefik.Routers.Router1.Rule": "foobar",
"traefik.Routers.Router1.Service": "foobar",
"traefik.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar",
"traefik.Services.Service0.LoadBalancer.HealthCheck.Headers.name1": "foobar",
"traefik.Services.Service0.LoadBalancer.HealthCheck.Hostname": "foobar",
"traefik.Services.Service0.LoadBalancer.HealthCheck.Interval": "foobar",
@ -884,7 +888,8 @@ func TestEncode(t *testing.T) {
"traefik.Services.Service0.LoadBalancer.Method": "foobar",
"traefik.Services.Service0.LoadBalancer.PassHostHeader": "true",
"traefik.Services.Service0.LoadBalancer.ResponseForwarding.FlushInterval": "foobar",
"traefik.Services.Service0.LoadBalancer.server.URL": "foobar",
"traefik.Services.Service0.LoadBalancer.server.Port": "8080",
"traefik.Services.Service0.LoadBalancer.server.Scheme": "foobar",
"traefik.Services.Service0.LoadBalancer.server.Weight": "42",
"traefik.Services.Service0.LoadBalancer.Stickiness.CookieName": "foobar",
"traefik.Services.Service1.LoadBalancer.HealthCheck.Headers.name0": "foobar",
@ -898,7 +903,9 @@ func TestEncode(t *testing.T) {
"traefik.Services.Service1.LoadBalancer.Method": "foobar",
"traefik.Services.Service1.LoadBalancer.PassHostHeader": "true",
"traefik.Services.Service1.LoadBalancer.ResponseForwarding.FlushInterval": "foobar",
"traefik.Services.Service1.LoadBalancer.server.URL": "foobar",
"traefik.Services.Service1.LoadBalancer.server.Port": "8080",
"traefik.Services.Service1.LoadBalancer.server.Scheme": "foobar",
"traefik.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar",
"traefik.Services.Service1.LoadBalancer.server.Weight": "42",
}