Adds Docker provider support
Co-authored-by: Julien Salleyron <julien@containo.us>
This commit is contained in:
parent
8735263930
commit
b54c956c5e
78 changed files with 3476 additions and 5587 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue