Merge branch 'v1.5' into master

This commit is contained in:
Fernandez Ludovic 2017-12-15 22:16:48 +01:00
commit f6520727a3
44 changed files with 1035 additions and 463 deletions

View file

@ -42,6 +42,7 @@ type Service struct {
Name string
Tags []string
Nodes []string
Ports []int
}
type serviceUpdate struct {
@ -185,19 +186,25 @@ func (p *CatalogProvider) watchCatalogServices(stopCh <-chan struct{}, watchCh c
errorCh <- err
return
}
nodesID := getServiceIds(nodes)
ports := getServicePorts(nodes)
if service, ok := current[key]; ok {
service.Tags = value
service.Nodes = nodesID
service.Ports = ports
} else {
service := Service{
Name: key,
Tags: value,
Nodes: nodesID,
Ports: ports,
}
current[key] = service
}
}
// A critical note is that the return of a blocking request is no guarantee of a change.
// It is possible that there was an idempotent write that does not affect the result of the query.
// Thus it is required to do extra check for changes...
@ -304,8 +311,11 @@ func (p *CatalogProvider) getNodes(index map[string][]string) ([]catalogUpdate,
}
func hasChanged(current map[string]Service, previous map[string]Service) bool {
if len(current) != len(previous) {
return true
}
addedServiceKeys, removedServiceKeys := getChangedServiceKeys(current, previous)
return len(removedServiceKeys) > 0 || len(addedServiceKeys) > 0 || hasNodeOrTagsChanged(current, previous)
return len(removedServiceKeys) > 0 || len(addedServiceKeys) > 0 || hasServiceChanged(current, previous)
}
func getChangedServiceKeys(current map[string]Service, previous map[string]Service) ([]string, []string) {
@ -318,20 +328,24 @@ func getChangedServiceKeys(current map[string]Service, previous map[string]Servi
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string)
}
func hasNodeOrTagsChanged(current map[string]Service, previous map[string]Service) bool {
var added []string
var removed []string
func hasServiceChanged(current map[string]Service, previous map[string]Service) bool {
for key, value := range current {
if prevValue, ok := previous[key]; ok {
addedNodesKeys, removedNodesKeys := getChangedStringKeys(value.Nodes, prevValue.Nodes)
added = append(added, addedNodesKeys...)
removed = append(removed, removedNodesKeys...)
if len(addedNodesKeys) > 0 || len(removedNodesKeys) > 0 {
return true
}
addedTagsKeys, removedTagsKeys := getChangedStringKeys(value.Tags, prevValue.Tags)
added = append(added, addedTagsKeys...)
removed = append(removed, removedTagsKeys...)
if len(addedTagsKeys) > 0 || len(removedTagsKeys) > 0 {
return true
}
addedPortsKeys, removedPortsKeys := getChangedIntKeys(value.Ports, prevValue.Ports)
if len(addedPortsKeys) > 0 || len(removedPortsKeys) > 0 {
return true
}
}
}
return len(added) > 0 || len(removed) > 0
return false
}
func getChangedStringKeys(currState []string, prevState []string) ([]string, []string) {
@ -344,14 +358,32 @@ func getChangedStringKeys(currState []string, prevState []string) ([]string, []s
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string)
}
func getChangedIntKeys(currState []int, prevState []int) ([]int, []int) {
currKeySet := fun.Set(currState).(map[int]bool)
prevKeySet := fun.Set(prevState).(map[int]bool)
addedKeys := fun.Difference(currKeySet, prevKeySet).(map[int]bool)
removedKeys := fun.Difference(prevKeySet, currKeySet).(map[int]bool)
return fun.Keys(addedKeys).([]int), fun.Keys(removedKeys).([]int)
}
func getServiceIds(services []*api.CatalogService) []string {
var serviceIds []string
for _, service := range services {
serviceIds = append(serviceIds, service.ServiceID)
serviceIds = append(serviceIds, service.ID)
}
return serviceIds
}
func getServicePorts(services []*api.CatalogService) []int {
var servicePorts []int
for _, service := range services {
servicePorts = append(servicePorts, service.ServicePort)
}
return servicePorts
}
func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
health := p.client.Health()
opts := &api.QueryOptions{}
@ -364,7 +396,7 @@ func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
return p.nodeFilter(service, node)
}, data).([]*api.ServiceEntry)
//Merge tags of nodes matching constraints, in a single slice.
// Merge tags of nodes matching constraints, in a single slice.
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
return fun.Keys(fun.Union(
fun.Set(set),

View file

@ -28,7 +28,7 @@ func (p *CatalogProvider) buildConfiguration(catalog []catalogUpdate) *types.Con
"getTag": getTag,
"hasTag": hasTag,
"getEntryPoints": getEntryPoints,
"hasMaxconnAttributes": p.hasMaxconnAttributes,
"hasMaxconnAttributes": p.hasMaxConnAttributes,
}
var allNodes []*api.ServiceEntry
@ -69,7 +69,7 @@ func (p *CatalogProvider) setupFrontEndTemplate() {
}
func (p *CatalogProvider) getFrontendRule(service serviceUpdate) string {
customFrontendRule := p.getAttribute("frontend.rule", service.Attributes, "")
customFrontendRule := p.getAttribute(label.SuffixFrontendRule, service.Attributes, "")
if customFrontendRule == "" {
customFrontendRule = p.FrontEndRule
}
@ -102,16 +102,16 @@ func (p *CatalogProvider) getFrontendRule(service serviceUpdate) string {
}
func (p *CatalogProvider) getBasicAuth(tags []string) []string {
list := p.getAttribute("frontend.auth.basic", tags, "")
list := p.getAttribute(label.SuffixFrontendAuthBasic, tags, "")
if list != "" {
return strings.Split(list, ",")
}
return []string{}
}
func (p *CatalogProvider) hasMaxconnAttributes(attributes []string) bool {
amount := p.getAttribute("backend.maxconn.amount", attributes, "")
extractorfunc := p.getAttribute("backend.maxconn.extractorfunc", attributes, "")
func (p *CatalogProvider) hasMaxConnAttributes(attributes []string) bool {
amount := p.getAttribute(label.SuffixBackendMaxConnAmount, attributes, "")
extractorfunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, attributes, "")
return amount != "" && extractorfunc != ""
}

View file

@ -1166,7 +1166,7 @@ func TestHasNodeOrTagschanged(t *testing.T) {
expected: false,
},
{
desc: "Change detected con tags",
desc: "Change detected on tags",
current: map[string]Service{
"foo-service": {
Name: "foo",
@ -1183,6 +1183,66 @@ func TestHasNodeOrTagschanged(t *testing.T) {
},
expected: true,
},
{
desc: "Change detected on ports",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
Ports: []int{80},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
Ports: []int{81},
},
},
expected: true,
},
{
desc: "Change detected on ports",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo=bar"},
Ports: []int{80},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
Ports: []int{81, 82},
},
},
expected: true,
},
{
desc: "No Change detected",
current: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
Ports: []int{80},
},
},
previous: map[string]Service{
"foo-service": {
Name: "foo",
Nodes: []string{"node1"},
Tags: []string{"foo"},
Ports: []int{80},
},
},
expected: false,
},
}
for _, test := range testCases {
@ -1190,7 +1250,7 @@ func TestHasNodeOrTagschanged(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := hasNodeOrTagsChanged(test.current, test.previous)
actual := hasServiceChanged(test.current, test.previous)
assert.Equal(t, test.expected, actual)
})
}

View file

@ -13,18 +13,22 @@ import (
func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.Configuration {
var DockerFuncMap = template.FuncMap{
"getBackend": getBackend,
"getIPAddress": p.getIPAddress,
"getPort": getPort,
"getWeight": getFuncStringLabel(label.TraefikWeight, label.DefaultWeight),
"getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain),
"getProtocol": getFuncStringLabel(label.TraefikProtocol, label.DefaultProtocol),
"getPassHostHeader": getFuncStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
"getPriority": getFuncStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
"getFrontendRule": p.getFrontendRule,
"getRedirect": getFuncStringLabel(label.TraefikFrontendRedirect, label.DefaultFrontendRedirect),
"getBackend": getBackend,
"getIPAddress": p.getIPAddress,
"getPort": getPort,
"getWeight": getFuncStringLabel(label.TraefikWeight, label.DefaultWeight),
"getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain),
"getProtocol": getFuncStringLabel(label.TraefikProtocol, label.DefaultProtocol),
"getPassHostHeader": getFuncStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
"getPriority": getFuncStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
"getFrontendRule": p.getFrontendRule,
"hasRedirect": hasRedirect,
"getRedirectEntryPoint": getFuncStringLabel(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
"getRedirectRegex": getFuncStringLabel(label.TraefikFrontendRedirectRegex, ""),
"getRedirectReplacement": getFuncStringLabel(label.TraefikFrontendRedirectReplacement, ""),
"hasCircuitBreakerLabel": hasFunc(label.TraefikBackendCircuitBreakerExpression),
"getCircuitBreakerExpression": getFuncStringLabel(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
"hasLoadBalancerLabel": hasLoadBalancerLabel,
@ -36,8 +40,6 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
"hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness),
"getStickinessCookieName": getFuncStringLabel(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
"isBackendLBSwarm": isBackendLBSwarm, // FIXME DEAD ?
"getServiceBackend": getServiceBackend,
"getServiceRedirect": getFuncServiceStringLabel(label.SuffixFrontendRedirect, label.DefaultFrontendRedirect),
"getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange),
"hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders),
@ -81,20 +83,25 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
"hasIsDevelopmentHeaders": hasFunc(label.TraefikFrontendIsDevelopment),
"getIsDevelopmentHeaders": getFuncBoolLabel(label.TraefikFrontendIsDevelopment, false),
"hasServices": hasServices,
"getServiceNames": getServiceNames,
"getServicePort": getServicePort,
"hasServiceRequestHeaders": hasFuncServiceLabel(label.SuffixFrontendRequestHeaders),
"getServiceRequestHeaders": getFuncServiceMapLabel(label.SuffixFrontendRequestHeaders),
"hasServiceResponseHeaders": hasFuncServiceLabel(label.SuffixFrontendResponseHeaders),
"getServiceResponseHeaders": getFuncServiceMapLabel(label.SuffixFrontendResponseHeaders),
"getServiceWeight": getFuncServiceStringLabel(label.SuffixWeight, label.DefaultWeight),
"getServiceProtocol": getFuncServiceStringLabel(label.SuffixProtocol, label.DefaultProtocol),
"getServiceEntryPoints": getFuncServiceSliceStringLabel(label.SuffixFrontendEntryPoints),
"getServiceBasicAuth": getFuncServiceSliceStringLabel(label.SuffixFrontendAuthBasic),
"getServiceFrontendRule": p.getServiceFrontendRule,
"getServicePassHostHeader": getFuncServiceStringLabel(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeader),
"getServicePriority": getFuncServiceStringLabel(label.SuffixFrontendPriority, label.DefaultFrontendPriority),
"hasServices": hasServices,
"getServiceBackend": getServiceBackend,
"getServiceNames": getServiceNames,
"getServicePort": getServicePort,
"hasServiceRequestHeaders": hasFuncServiceLabel(label.SuffixFrontendRequestHeaders),
"getServiceRequestHeaders": getFuncServiceMapLabel(label.SuffixFrontendRequestHeaders),
"hasServiceResponseHeaders": hasFuncServiceLabel(label.SuffixFrontendResponseHeaders),
"getServiceResponseHeaders": getFuncServiceMapLabel(label.SuffixFrontendResponseHeaders),
"getServiceWeight": getFuncServiceStringLabel(label.SuffixWeight, label.DefaultWeight),
"getServiceProtocol": getFuncServiceStringLabel(label.SuffixProtocol, label.DefaultProtocol),
"getServiceEntryPoints": getFuncServiceSliceStringLabel(label.SuffixFrontendEntryPoints),
"getServiceBasicAuth": getFuncServiceSliceStringLabel(label.SuffixFrontendAuthBasic),
"getServiceFrontendRule": p.getServiceFrontendRule,
"getServicePassHostHeader": getFuncServiceStringLabel(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeader),
"getServicePriority": getFuncServiceStringLabel(label.SuffixFrontendPriority, label.DefaultFrontendPriority),
"hasServiceRedirect": hasServiceRedirect,
"getServiceRedirectEntryPoint": getFuncServiceStringLabel(label.SuffixFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
"getServiceRedirectReplacement": getFuncServiceStringLabel(label.SuffixFrontendRedirectReplacement, ""),
"getServiceRedirectRegex": getFuncServiceStringLabel(label.SuffixFrontendRedirectRegex, ""),
}
// filter containers
filteredContainers := fun.Filter(func(container dockerData) bool {
@ -125,11 +132,11 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
Servers map[string][]dockerData
Domain string
}{
filteredContainers,
frontends,
backends,
servers,
p.Domain,
Containers: filteredContainers,
Frontends: frontends,
Backends: backends,
Servers: servers,
Domain: p.Domain,
}
configuration, err := p.GetConfiguration("templates/docker.tmpl", DockerFuncMap, templateObjects)

View file

@ -167,6 +167,11 @@ func isBackendLBSwarm(container dockerData) bool {
return label.GetBoolValue(container.Labels, labelBackendLoadBalancerSwarm, false)
}
func hasRedirect(container dockerData) bool {
return label.Has(container.Labels, label.TraefikFrontendRedirectEntryPoint) ||
label.Has(container.Labels, label.TraefikFrontendRedirectReplacement) && label.Has(container.Labels, label.TraefikFrontendRedirectRegex)
}
// Label functions
func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {

View file

@ -10,6 +10,8 @@ import (
docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDockerLoadDockerConfig(t *testing.T) {
@ -39,7 +41,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost",
@ -64,10 +65,10 @@ func TestDockerLoadDockerConfig(t *testing.T) {
containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackend: "foobar",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendRedirect: "https",
label.TraefikBackend: "foobar",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendRedirectEntryPoint: "https",
}),
ports(nat.PortMap{
"80/tcp": {},
@ -91,7 +92,9 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost",
@ -103,7 +106,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test2-docker-localhost-1": {
Rule: "Host:test2.docker.localhost",
@ -151,7 +153,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost",
@ -197,13 +198,10 @@ func TestDockerLoadDockerConfig(t *testing.T) {
ExposedByDefault: true,
}
actualConfig := provider.buildConfiguration(dockerDataList)
// Compare backends
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
t.Errorf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
}
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
t.Errorf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
}
require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}

View file

@ -9,6 +9,8 @@ import (
"github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSwarmGetFrontendName(t *testing.T) {
@ -416,7 +418,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost",
@ -447,11 +448,11 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
swarmService(
serviceName("test1"),
serviceLabels(map[string]string{
label.TraefikPort: "80",
label.TraefikBackend: "foobar",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendRedirect: "https",
label.TraefikPort: "80",
label.TraefikBackend: "foobar",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendRedirectEntryPoint: "https",
}),
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "127.0.0.1/24")),
@ -472,7 +473,9 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost",
@ -484,7 +487,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"route-frontend-Host-test2-docker-localhost-1": {
Rule: "Host:test2.docker.localhost",
@ -531,14 +533,12 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
ExposedByDefault: true,
SwarmMode: true,
}
actualConfig := provider.buildConfiguration(dockerDataList)
// Compare backends
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
t.Errorf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
}
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
t.Errorf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
}
require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}

View file

@ -86,6 +86,16 @@ func getServicePort(container dockerData, serviceName string) string {
return getPort(container)
}
func hasServiceRedirect(container dockerData, serviceName string) bool {
serviceLabels := getServiceLabels(container, serviceName)
if len(serviceLabels) == 0 {
return false
}
return label.Has(serviceLabels, label.SuffixFrontendRedirectEntryPoint) ||
label.Has(serviceLabels, label.SuffixFrontendRedirectRegex) && label.Has(serviceLabels, label.SuffixFrontendRedirectReplacement)
}
// Service label functions
func getFuncServiceMapLabel(labelSuffix string) func(container dockerData, serviceName string) map[string]string {

View file

@ -121,7 +121,7 @@ func TestDockerGetFuncServiceStringLabel(t *testing.T) {
actual := getFuncServiceStringLabel(test.suffixLabel, test.defaultValue)(dData, "myservice")
if actual != test.expected {
t.Fatalf("got %q, expected %q", actual, test.expected)
t.Errorf("got %q, expected %q", actual, test.expected)
}
})
}
@ -164,7 +164,7 @@ func TestDockerGetFuncServiceSliceStringLabel(t *testing.T) {
actual := getFuncServiceSliceStringLabel(test.suffixLabel)(dData, "myservice")
if !reflect.DeepEqual(actual, test.expected) {
t.Fatalf("for container %q: got %q, expected %q", dData.Name, actual, test.expected)
t.Errorf("for container %q: got %q, expected %q", dData.Name, actual, test.expected)
}
})
}

View file

@ -1,7 +1,6 @@
package docker
import (
"reflect"
"strconv"
"testing"
@ -9,6 +8,8 @@ import (
"github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDockerGetServicePort(t *testing.T) {
@ -41,7 +42,7 @@ func TestDockerGetServicePort(t *testing.T) {
dData := parseContainer(test.container)
actual := getServicePort(dData, "myservice")
if actual != test.expected {
t.Fatalf("expected %q, got %q", test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
@ -79,7 +80,7 @@ func TestDockerGetServiceFrontendRule(t *testing.T) {
dData := parseContainer(test.container)
actual := provider.getServiceFrontendRule(dData, "myservice")
if actual != test.expected {
t.Fatalf("expected %q, got %q", test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
@ -115,7 +116,7 @@ func TestDockerGetServiceBackend(t *testing.T) {
dData := parseContainer(test.container)
actual := getServiceBackend(dData, "myservice")
if actual != test.expected {
t.Fatalf("expected %q, got %q", test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
@ -137,10 +138,10 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
containerJSON(
name("foo"),
labels(map[string]string{
"traefik.service.port": "2503",
"traefik.service.frontend.entryPoints": "http,https",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect": "https",
"traefik.service.port": "2503",
"traefik.service.frontend.entryPoints": "http,https",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect.entryPoint": "https",
}),
ports(nat.PortMap{
"80/tcp": {},
@ -154,7 +155,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"service-service": {
Rule: "Host:foo.docker.localhost",
@ -179,16 +182,16 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
containerJSON(
name("test1"),
labels(map[string]string{
"traefik.service.port": "2503",
"traefik.service.protocol": "https",
"traefik.service.weight": "80",
"traefik.service.frontend.backend": "foobar",
"traefik.service.frontend.passHostHeader": "false",
"traefik.service.frontend.rule": "Path:/mypath",
"traefik.service.frontend.priority": "5000",
"traefik.service.frontend.entryPoints": "http,https,ws",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect": "https",
"traefik.service.port": "2503",
"traefik.service.protocol": "https",
"traefik.service.weight": "80",
"traefik.service.frontend.backend": "foobar",
"traefik.service.frontend.passHostHeader": "false",
"traefik.service.frontend.rule": "Path:/mypath",
"traefik.service.frontend.priority": "5000",
"traefik.service.frontend.entryPoints": "http,https,ws",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect.entryPoint": "https",
}),
ports(nat.PortMap{
"80/tcp": {},
@ -215,7 +218,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
Priority: 5000,
EntryPoints: []string{"http", "https", "ws"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"service-service": {
Rule: "Path:/mypath",
@ -227,7 +232,6 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{
"service-anotherservice": {
Rule: "Path:/anotherpath",
@ -274,13 +278,10 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
}
actualConfig := provider.buildConfiguration(dockerDataList)
// Compare backends
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
}
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
}
require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}

View file

@ -152,9 +152,22 @@ func priority(value int) func(*types.Frontend) {
}
}
func redirect(value string) func(*types.Frontend) {
func redirectEntryPoint(name string) func(*types.Frontend) {
return func(f *types.Frontend) {
f.Redirect = value
if f.Redirect == nil {
f.Redirect = &types.Redirect{}
}
f.Redirect.EntryPoint = name
}
}
func redirectRegex(regex, replacement string) func(*types.Frontend) {
return func(f *types.Frontend) {
if f.Redirect == nil {
f.Redirect = &types.Redirect{}
}
f.Redirect.Regex = regex
f.Redirect.Replacement = replacement
}
}

View file

@ -203,8 +203,6 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
whitelistSourceRange := label.GetSliceStringValue(i.Annotations, annotationKubernetesWhitelistSourceRange)
entryPointRedirect := i.Annotations[label.TraefikFrontendRedirect]
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
if err != nil {
@ -245,7 +243,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
Priority: priority,
BasicAuth: basicAuthCreds,
WhitelistSourceRange: whitelistSourceRange,
Redirect: entryPointRedirect,
Redirect: getFrontendRedirect(i),
EntryPoints: entryPoints,
Headers: headers,
}
@ -470,3 +468,23 @@ func equalPorts(servicePort v1.ServicePort, ingressPort intstr.IntOrString) bool
func shouldProcessIngress(ingressClass string) bool {
return ingressClass == "" || ingressClass == "traefik"
}
func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect {
frontendRedirectEntryPoint, ok := i.Annotations[label.TraefikFrontendRedirectEntryPoint]
frep := ok && len(frontendRedirectEntryPoint) > 0
frontendRedirectRegex, ok := i.Annotations[label.TraefikFrontendRedirectRegex]
frrg := ok && len(frontendRedirectRegex) > 0
frontendRedirectReplacement, ok := i.Annotations[label.TraefikFrontendRedirectReplacement]
frrp := ok && len(frontendRedirectReplacement) > 0
if frep || frrg && frrp {
return &types.Redirect{
EntryPoint: frontendRedirectEntryPoint,
Regex: frontendRedirectRegex,
Replacement: frontendRedirectReplacement,
}
}
return nil
}

View file

@ -608,7 +608,7 @@ func TestIngressAnnotations(t *testing.T) {
buildIngress(
iNamespace("testing"),
iAnnotation(annotationKubernetesIngressClass, "traefik"),
iAnnotation(label.TraefikFrontendRedirect, "https"),
iAnnotation(label.TraefikFrontendRedirectEntryPoint, "https"),
iRules(
iRule(
iHost("redirect"),
@ -752,7 +752,7 @@ func TestIngressAnnotations(t *testing.T) {
),
frontend("redirect/https",
passHostHeader(),
redirect("https"),
redirectEntryPoint("https"),
routes(
route("/https", "PathPrefix:/https"),
route("redirect", "Host:redirect")),
@ -1182,6 +1182,7 @@ func TestBasicAuthInTemplate(t *testing.T) {
require.NoError(t, err, "error loading ingresses")
actual = provider.loadConfig(*actual)
require.NotNil(t, actual)
got := actual.Frontends["basic/auth"].BasicAuth
if !reflect.DeepEqual(got, []string{"myUser:myEncodedPW"}) {
t.Fatalf("unexpected credentials: %+v", got)

View file

@ -22,7 +22,7 @@ const (
DefaultPassHostHeader = "true"
DefaultFrontendPriority = "0"
DefaultCircuitBreakerExpression = "NetworkErrorRatio() > 1"
DefaultFrontendRedirect = ""
DefaultFrontendRedirectEntryPoint = ""
DefaultBackendLoadBalancerMethod = "wrr"
DefaultBackendMaxconnExtractorFunc = "request.host"
DefaultBackendLoadbalancerStickinessCookieName = ""

View file

@ -48,7 +48,9 @@ const (
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPassTLSCert = "frontend.passTLSCert"
SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRedirect = "frontend.redirect"
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRule = "frontend.rule"
SuffixFrontendRuleType = "frontend.rule.type"
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange"
@ -79,7 +81,9 @@ const (
TraefikFrontendPriority = Prefix + SuffixFrontendPriority
TraefikFrontendRule = Prefix + SuffixFrontendRule
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
TraefikFrontendRedirect = Prefix + SuffixFrontendRedirect
TraefikFrontendRedirectEntryPoint = Prefix + SuffixFrontendRedirectEntryPoint
TraefikFrontendRedirectRegex = Prefix + SuffixFrontendRedirectRegex
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
TraefikFrontendValue = Prefix + SuffixFrontendValue
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders

View file

@ -27,15 +27,18 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati
"getFrontendRule": p.getFrontendRule,
"hasCircuitBreakerLabel": hasFunc(label.TraefikBackendCircuitBreakerExpression),
"getCircuitBreakerExpression": getFuncString(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
"hasLoadBalancerLabel": hasLoadBalancerLabel, // OK
"getLoadBalancerMethod": getFuncString(label.TraefikFrontendRedirect, label.DefaultBackendLoadBalancerMethod),
"hasMaxConnLabels": hasMaxConnLabels, // OK
"hasLoadBalancerLabel": hasLoadBalancerLabel,
"getLoadBalancerMethod": getFuncString(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
"hasMaxConnLabels": hasMaxConnLabels,
"getMaxConnAmount": getFuncInt64(label.TraefikBackendMaxConnAmount, math.MaxInt64),
"getMaxConnExtractorFunc": getFuncString(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
"getSticky": getSticky, // deprecated
"hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness),
"getStickinessCookieName": getFuncString(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
"getRedirect": getFuncString(label.TraefikFrontendRedirect, label.DefaultFrontendRedirect),
"hasRedirect": hasRedirect,
"getRedirectEntryPoint": getFuncString(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
"getRedirectRegex": getFuncString(label.TraefikFrontendRedirectRegex, ""),
"getRedirectReplacement": getFuncString(label.TraefikFrontendRedirectReplacement, ""),
}
// filter services
@ -116,7 +119,8 @@ func (p *Provider) getFrontendName(service rancherData) string {
}
// TODO: Deprecated
// Deprecated replaced by Stickiness
// replaced by Stickiness
// Deprecated
func getSticky(service rancherData) string {
if label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
@ -143,6 +147,11 @@ func getBackend(service rancherData) string {
return provider.Normalize(backend)
}
func hasRedirect(service rancherData) bool {
return label.Has(service.Labels, label.TraefikFrontendRedirectEntryPoint) ||
label.Has(service.Labels, label.TraefikFrontendRedirectRegex) && label.Has(service.Labels, label.TraefikFrontendRedirectReplacement)
}
// Label functions
func getFuncString(labelName string, defaultValue string) func(service rancherData) string {

View file

@ -6,6 +6,7 @@ import (
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProviderServiceFilter(t *testing.T) {
@ -361,9 +362,9 @@ func TestProviderLoadRancherConfig(t *testing.T) {
{
Name: "test/service",
Labels: map[string]string{
label.TraefikPort: "80",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendRedirect: "https",
label.TraefikPort: "80",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendRedirectEntryPoint: "https",
},
Health: "healthy",
Containers: []string{"127.0.0.1"},
@ -376,7 +377,9 @@ func TestProviderLoadRancherConfig(t *testing.T) {
EntryPoints: []string{},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Priority: 0,
Redirect: "https",
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"route-frontend-Host-test-service-rancher-localhost": {
Rule: "Host:test.service.rancher.localhost",
@ -405,9 +408,77 @@ func TestProviderLoadRancherConfig(t *testing.T) {
t.Parallel()
actualConfig := provider.buildConfiguration(test.services)
require.NotNil(t, actualConfig)
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestHasRedirect(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected bool
}{
{
desc: "without redirect labels",
service: rancherData{
Name: "test-service",
},
expected: false,
},
{
desc: "with Redirect EntryPoint label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
},
},
expected: true,
},
{
desc: "with Redirect regex label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRedirectRegex: `(.+)`,
},
},
expected: false,
},
{
desc: "with Redirect replacement label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRedirectReplacement: "$1",
},
},
expected: false,
},
{
desc: "with Redirect regex & replacement labels",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRedirectRegex: `(.+)`,
label.TraefikFrontendRedirectReplacement: "$1",
},
},
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := hasRedirect(test.service)
assert.Equal(t, test.expected, actual)
})
}
}