1
0
Fork 0

Enables the use of elements declared in other providers

This commit is contained in:
Gérald Croës 2019-01-15 05:28:04 -08:00 committed by Traefiker Bot
parent a79d6aa669
commit 8735263930
16 changed files with 753 additions and 94 deletions

View file

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"strings"
"github.com/containous/alice"
"github.com/containous/traefik/config"
@ -26,9 +27,16 @@ import (
"github.com/containous/traefik/middlewares/stripprefix"
"github.com/containous/traefik/middlewares/stripprefixregex"
"github.com/containous/traefik/middlewares/tracing"
"github.com/containous/traefik/server/internal"
"github.com/pkg/errors"
)
type middlewareStackType int
const (
middlewareStackKey middlewareStackType = iota
)
// Builder the middleware builder
type Builder struct {
configs map[string]*config.Middleware
@ -45,22 +53,40 @@ func NewBuilder(configs map[string]*config.Middleware, serviceBuilder serviceBui
}
// BuildChain creates a middleware chain
func (b *Builder) BuildChain(ctx context.Context, middlewares []string) (*alice.Chain, error) {
func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.Chain {
chain := alice.New()
for _, middlewareName := range middlewares {
if _, ok := b.configs[middlewareName]; !ok {
return nil, fmt.Errorf("middleware %q does not exist", middlewareName)
}
middlewareName := internal.GetQualifiedName(ctx, middlewareName)
constructorContext := internal.AddProviderInContext(ctx, middlewareName)
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
var err error
if constructorContext, err = checkRecursivity(constructorContext, middlewareName); err != nil {
return nil, err
}
constructor, err := b.buildConstructor(ctx, middlewareName, *b.configs[middlewareName])
if err != nil {
return nil, err
}
if constructor != nil {
chain = chain.Append(constructor)
}
if _, ok := b.configs[middlewareName]; !ok {
return nil, fmt.Errorf("middleware %q does not exist", middlewareName)
}
constructor, err := b.buildConstructor(constructorContext, middlewareName, *b.configs[middlewareName])
if err != nil {
return nil, fmt.Errorf("error while instanciation of %s: %v", middlewareName, err)
}
return constructor(next)
})
}
return &chain, nil
return &chain
}
func checkRecursivity(ctx context.Context, middlewareName string) (context.Context, error) {
currentStack, ok := ctx.Value(middlewareStackKey).([]string)
if !ok {
currentStack = []string{}
}
if inSlice(middlewareName, currentStack) {
return ctx, fmt.Errorf("could not instantiate middleware %s: recursion detected in %s", middlewareName, strings.Join(append(currentStack, middlewareName), "->"))
}
return context.WithValue(ctx, middlewareStackKey, append(currentStack, middlewareName)), nil
}
func (b *Builder) buildConstructor(ctx context.Context, middlewareName string, config config.Middleware) (alice.Constructor, error) {
@ -290,3 +316,12 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string, c
return tracing.Wrap(ctx, middleware), nil
}
func inSlice(element string, stack []string) bool {
for _, value := range stack {
if value == element {
return true
}
}
return false
}

View file

@ -3,9 +3,13 @@ package middleware
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/config"
"github.com/containous/traefik/server/internal"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -67,10 +71,8 @@ func TestMiddlewaresRegistry_BuildChainNilConfig(t *testing.T) {
}
middlewaresBuilder := NewBuilder(testConfig, nil)
chain, err := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"})
require.NoError(t, err)
_, err = chain.Then(nil)
chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"})
_, err := chain.Then(nil)
require.NoError(t, err)
}
@ -125,3 +127,254 @@ func TestMiddlewaresRegistry_BuildMiddlewareAddPrefix(t *testing.T) {
})
}
}
func TestChainWithContext(t *testing.T) {
testCases := []struct {
desc string
buildChain []string
configuration map[string]*config.Middleware
expected map[string]string
contextProvider string
expectedError error
}{
{
desc: "Simple middleware",
buildChain: []string{"middleware-1"},
configuration: map[string]*config.Middleware{
"middleware-1": {
Headers: &config.Headers{
CustomRequestHeaders: map[string]string{"middleware-1": "value-middleware-1"},
},
},
},
expected: map[string]string{"middleware-1": "value-middleware-1"},
},
{
desc: "Middleware that references a chain",
buildChain: []string{"middleware-chain-1"},
configuration: map[string]*config.Middleware{
"middleware-1": {
Headers: &config.Headers{
CustomRequestHeaders: map[string]string{"middleware-1": "value-middleware-1"},
},
},
"middleware-chain-1": {
Chain: &config.Chain{
Middlewares: []string{"middleware-1"},
},
},
},
expected: map[string]string{"middleware-1": "value-middleware-1"},
},
{
desc: "Should prefix the middlewareName with the provider in the context",
buildChain: []string{"middleware-1"},
configuration: map[string]*config.Middleware{
"provider-1.middleware-1": {
Headers: &config.Headers{
CustomRequestHeaders: map[string]string{"provider-1.middleware-1": "value-middleware-1"},
},
},
},
expected: map[string]string{"provider-1.middleware-1": "value-middleware-1"},
contextProvider: "provider-1",
},
{
desc: "Should not prefix a qualified middlewareName with the provider in the context",
buildChain: []string{"provider-1.middleware-1"},
configuration: map[string]*config.Middleware{
"provider-1.middleware-1": {
Headers: &config.Headers{
CustomRequestHeaders: map[string]string{"provider-1.middleware-1": "value-middleware-1"},
},
},
},
expected: map[string]string{"provider-1.middleware-1": "value-middleware-1"},
contextProvider: "provider-1",
},
{
desc: "Should be context aware if a chain references another middleware",
buildChain: []string{"provider-1.middleware-chain-1"},
configuration: map[string]*config.Middleware{
"provider-1.middleware-1": {
Headers: &config.Headers{
CustomRequestHeaders: map[string]string{"middleware-1": "value-middleware-1"},
},
},
"provider-1.middleware-chain-1": {
Chain: &config.Chain{
Middlewares: []string{"middleware-1"},
},
},
},
expected: map[string]string{"middleware-1": "value-middleware-1"},
},
{
desc: "Should handle nested chains with different context",
buildChain: []string{"provider-1.middleware-chain-1", "middleware-chain-1"},
configuration: map[string]*config.Middleware{
"provider-1.middleware-1": {
Headers: &config.Headers{
CustomRequestHeaders: map[string]string{"middleware-1": "value-middleware-1"},
},
},
"provider-1.middleware-2": {
Headers: &config.Headers{
CustomRequestHeaders: map[string]string{"middleware-2": "value-middleware-2"},
},
},
"provider-1.middleware-chain-1": {
Chain: &config.Chain{
Middlewares: []string{"middleware-1"},
},
},
"provider-1.middleware-chain-2": {
Chain: &config.Chain{
Middlewares: []string{"middleware-2"},
},
},
"provider-2.middleware-chain-1": {
Chain: &config.Chain{
Middlewares: []string{"provider-1.middleware-2", "provider-1.middleware-chain-2"},
},
},
},
expected: map[string]string{"middleware-1": "value-middleware-1", "middleware-2": "value-middleware-2"},
contextProvider: "provider-2",
},
{
desc: "Detects recursion in Middleware chain",
buildChain: []string{"m1"},
configuration: map[string]*config.Middleware{
"ok": {
Retry: &config.Retry{},
},
"m1": {
Chain: &config.Chain{
Middlewares: []string{"m2"},
},
},
"m2": {
Chain: &config.Chain{
Middlewares: []string{"ok", "m3"},
},
},
"m3": {
Chain: &config.Chain{
Middlewares: []string{"m1"},
},
},
},
expectedError: errors.New("could not instantiate middleware m1: recursion detected in m1->m2->m3->m1"),
},
{
desc: "Detects recursion in Middleware chain",
buildChain: []string{"provider.m1"},
configuration: map[string]*config.Middleware{
"provider2.ok": {
Retry: &config.Retry{},
},
"provider.m1": {
Chain: &config.Chain{
Middlewares: []string{"provider2.m2"},
},
},
"provider2.m2": {
Chain: &config.Chain{
Middlewares: []string{"ok", "provider.m3"},
},
},
"provider.m3": {
Chain: &config.Chain{
Middlewares: []string{"m1"},
},
},
},
expectedError: errors.New("could not instantiate middleware provider.m1: recursion detected in provider.m1->provider2.m2->provider.m3->provider.m1"),
},
{
buildChain: []string{"ok", "m0"},
configuration: map[string]*config.Middleware{
"ok": {
Retry: &config.Retry{},
},
"m0": {
Chain: &config.Chain{
Middlewares: []string{"m0"},
},
},
},
expectedError: errors.New("could not instantiate middleware m0: recursion detected in m0->m0"),
},
{
desc: "Detects MiddlewareChain that references a Chain that references a Chain with a missing middleware",
buildChain: []string{"m0"},
configuration: map[string]*config.Middleware{
"m0": {
Chain: &config.Chain{
Middlewares: []string{"m1"},
},
},
"m1": {
Chain: &config.Chain{
Middlewares: []string{"m2"},
},
},
"m2": {
Chain: &config.Chain{
Middlewares: []string{"m3"},
},
},
"m3": {
Chain: &config.Chain{
Middlewares: []string{"m2"},
},
},
},
expectedError: errors.New("could not instantiate middleware m2: recursion detected in m0->m1->m2->m3->m2"),
},
{
desc: "--",
buildChain: []string{"m0"},
configuration: map[string]*config.Middleware{
"m0": {
Chain: &config.Chain{
Middlewares: []string{"m0"},
},
},
},
expectedError: errors.New("could not instantiate middleware m0: recursion detected in m0->m0"),
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ctx := context.Background()
if len(test.contextProvider) > 0 {
ctx = internal.AddProviderInContext(ctx, test.contextProvider+".foobar")
}
builder := NewBuilder(test.configuration, nil)
result := builder.BuildChain(ctx, test.buildChain)
handlers, err := result.Then(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }))
if test.expectedError != nil {
require.NotNil(t, err)
require.Equal(t, test.expectedError.Error(), err.Error())
} else {
require.NoError(t, err)
recorder := httptest.NewRecorder()
request, _ := http.NewRequest(http.MethodGet, "http://foo/", nil)
handlers.ServeHTTP(recorder, request)
for key, value := range test.expected {
assert.Equal(t, value, request.Header.Get(key))
}
}
})
}
}