1
0
Fork 0

Segments Labels: Rancher & Marathon

This commit is contained in:
Ludovic Fernandez 2018-03-26 15:32:04 +02:00 committed by Traefiker Bot
parent 16bb9b6836
commit 0ea007b26f
31 changed files with 4288 additions and 3656 deletions

View file

@ -2,7 +2,6 @@ package rancher
import (
"fmt"
"math"
"strconv"
"strings"
"text/template"
@ -14,63 +13,32 @@ import (
"github.com/containous/traefik/types"
)
func (p *Provider) buildConfiguration(services []rancherData) *types.Configuration {
func (p *Provider) buildConfigurationV2(services []rancherData) *types.Configuration {
var RancherFuncMap = template.FuncMap{
"getDomain": getFuncString(label.TraefikDomain, p.Domain),
"getLabelValue": label.GetStringValue,
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),
// Backend functions
"getCircuitBreaker": getCircuitBreaker,
"getLoadBalancer": getLoadBalancer,
"getMaxConn": getMaxConn,
"getHealthCheck": getHealthCheck,
"getBuffering": getBuffering,
"getCircuitBreaker": label.GetCircuitBreaker,
"getLoadBalancer": label.GetLoadBalancer,
"getMaxConn": label.GetMaxConn,
"getHealthCheck": label.GetHealthCheck,
"getBuffering": label.GetBuffering,
"getServers": getServers,
// TODO Deprecated [breaking]
"getPort": getFuncString(label.TraefikPort, ""),
// TODO Deprecated [breaking]
"getProtocol": getFuncString(label.TraefikProtocol, label.DefaultProtocol),
// TODO Deprecated [breaking]
"getWeight": getFuncInt(label.TraefikWeight, label.DefaultWeightInt),
// TODO Deprecated [breaking]
"hasCircuitBreakerLabel": hasFunc(label.TraefikBackendCircuitBreakerExpression),
// TODO Deprecated [breaking]
"getCircuitBreakerExpression": getFuncString(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
// TODO Deprecated [breaking]
"hasLoadBalancerLabel": hasLoadBalancerLabel,
// TODO Deprecated [breaking]
"getLoadBalancerMethod": getFuncString(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
// TODO Deprecated [breaking]
"hasMaxConnLabels": hasMaxConnLabels,
// TODO Deprecated [breaking]
"getMaxConnAmount": getFuncInt64(label.TraefikBackendMaxConnAmount, 0),
// TODO Deprecated [breaking]
"getMaxConnExtractorFunc": getFuncString(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
// TODO Deprecated [breaking]
"getSticky": getSticky,
// TODO Deprecated [breaking]
"hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness),
// TODO Deprecated [breaking]
"getStickinessCookieName": getFuncString(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
// Frontend functions
"getBackend": getBackendName, // TODO Deprecated [breaking] replaced by getBackendName
"getBackendName": getBackendName,
"getFrontendRule": p.getFrontendRule,
"getPriority": getFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic),
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getRedirect": getRedirect,
"getHeaders": getHeaders,
"getWhiteList": getWhiteList,
// TODO Deprecated [breaking]
"getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange),
"getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
"getErrorPages": label.GetErrorPages,
"getRateLimit": label.GetRateLimit,
"getRedirect": label.GetRedirect,
"getHeaders": label.GetHeaders,
"getWhiteList": label.GetWhiteList,
}
// filter services
@ -80,10 +48,16 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati
backends := map[string]rancherData{}
for _, service := range filteredServices {
frontendName := p.getFrontendName(service)
frontends[frontendName] = service
backendName := getBackendName(service)
backends[backendName] = service
segmentProperties := label.ExtractTraefikLabels(service.Labels)
for segmentName, labels := range segmentProperties {
service.SegmentLabels = labels
service.SegmentName = segmentName
frontendName := p.getFrontendName(service)
frontends[frontendName] = service
backendName := getBackendName(service)
backends[backendName] = service
}
}
templateObjects := struct {
@ -105,9 +79,19 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati
}
func (p *Provider) serviceFilter(service rancherData) bool {
if service.Labels[label.TraefikPort] == "" {
log.Debugf("Filtering service %s without traefik.port label", service.Name)
return false
segmentProperties := label.ExtractTraefikLabels(service.Labels)
for segmentName, labels := range segmentProperties {
_, err := checkSegmentPort(labels, segmentName)
if err != nil {
log.Debugf("Filtering service %s %s without traefik.port label", service.Name, segmentName)
return false
}
if len(p.getFrontendRule(service)) == 0 {
log.Debugf("Filtering container with empty frontend rule %s %s", service.Name, segmentName)
return false
}
}
if !label.IsEnabled(service.Labels, p.ExposedByDefault) {
@ -145,112 +129,37 @@ func (p *Provider) getFrontendRule(service rancherData) string {
}
func (p *Provider) getFrontendName(service rancherData) string {
return provider.Normalize(p.getFrontendRule(service))
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getSticky(service rancherData) bool {
if label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
var name string
if len(service.SegmentName) > 0 {
name = getBackendName(service)
} else {
name = p.getFrontendRule(service)
}
return label.GetBoolValue(service.Labels, label.TraefikBackendLoadBalancerSticky, false)
}
// Deprecated
func hasLoadBalancerLabel(service rancherData) bool {
method := label.Has(service.Labels, label.TraefikBackendLoadBalancerMethod)
sticky := label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky)
stickiness := label.Has(service.Labels, label.TraefikBackendLoadBalancerStickiness)
cookieName := label.Has(service.Labels, label.TraefikBackendLoadBalancerStickinessCookieName)
return method || sticky || stickiness || cookieName
}
// Deprecated
func hasMaxConnLabels(service rancherData) bool {
mca := label.Has(service.Labels, label.TraefikBackendMaxConnAmount)
mcef := label.Has(service.Labels, label.TraefikBackendMaxConnExtractorFunc)
return mca && mcef
return provider.Normalize(name)
}
func getBackendName(service rancherData) string {
backend := label.GetStringValue(service.Labels, label.TraefikBackend, service.Name)
if len(service.SegmentName) > 0 {
return getSegmentBackendName(service)
}
return getDefaultBackendName(service)
}
func getSegmentBackendName(service rancherData) string {
if value := label.GetStringValue(service.SegmentLabels, label.TraefikFrontendBackend, ""); len(value) > 0 {
return provider.Normalize(service.Name + "-" + value)
}
return provider.Normalize(service.Name + "-" + getDefaultBackendName(service) + "-" + service.SegmentName)
}
func getDefaultBackendName(service rancherData) string {
backend := label.GetStringValue(service.SegmentLabels, label.TraefikBackend, service.Name)
return provider.Normalize(backend)
}
func getCircuitBreaker(service rancherData) *types.CircuitBreaker {
circuitBreaker := label.GetStringValue(service.Labels, label.TraefikBackendCircuitBreakerExpression, "")
if len(circuitBreaker) == 0 {
return nil
}
return &types.CircuitBreaker{Expression: circuitBreaker}
}
func getLoadBalancer(service rancherData) *types.LoadBalancer {
if !label.HasPrefix(service.Labels, label.TraefikBackendLoadBalancer) {
return nil
}
method := label.GetStringValue(service.Labels, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod)
lb := &types.LoadBalancer{
Method: method,
Sticky: getSticky(service),
}
if label.GetBoolValue(service.Labels, label.TraefikBackendLoadBalancerStickiness, false) {
cookieName := label.GetStringValue(service.Labels, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName)
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
}
return lb
}
func getMaxConn(service rancherData) *types.MaxConn {
amount := label.GetInt64Value(service.Labels, label.TraefikBackendMaxConnAmount, math.MinInt64)
extractorFunc := label.GetStringValue(service.Labels, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
if amount == math.MinInt64 || len(extractorFunc) == 0 {
return nil
}
return &types.MaxConn{
Amount: amount,
ExtractorFunc: extractorFunc,
}
}
func getHealthCheck(service rancherData) *types.HealthCheck {
path := label.GetStringValue(service.Labels, label.TraefikBackendHealthCheckPath, "")
if len(path) == 0 {
return nil
}
port := label.GetIntValue(service.Labels, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort)
interval := label.GetStringValue(service.Labels, label.TraefikBackendHealthCheckInterval, "")
return &types.HealthCheck{
Path: path,
Port: port,
Interval: interval,
}
}
func getBuffering(service rancherData) *types.Buffering {
if !label.HasPrefix(service.Labels, label.TraefikBackendBuffering) {
return nil
}
return &types.Buffering{
MaxRequestBodyBytes: label.GetInt64Value(service.Labels, label.TraefikBackendBufferingMaxRequestBodyBytes, 0),
MaxResponseBodyBytes: label.GetInt64Value(service.Labels, label.TraefikBackendBufferingMaxResponseBodyBytes, 0),
MemRequestBodyBytes: label.GetInt64Value(service.Labels, label.TraefikBackendBufferingMemRequestBodyBytes, 0),
MemResponseBodyBytes: label.GetInt64Value(service.Labels, label.TraefikBackendBufferingMemResponseBodyBytes, 0),
RetryExpression: label.GetStringValue(service.Labels, label.TraefikBackendBufferingRetryExpression, ""),
}
}
func getServers(service rancherData) map[string]types.Server {
var servers map[string]types.Server
@ -259,9 +168,9 @@ func getServers(service rancherData) map[string]types.Server {
servers = make(map[string]types.Server)
}
protocol := label.GetStringValue(service.Labels, label.TraefikProtocol, label.DefaultProtocol)
port := label.GetStringValue(service.Labels, label.TraefikPort, "")
weight := label.GetIntValue(service.Labels, label.TraefikWeight, label.DefaultWeightInt)
protocol := label.GetStringValue(service.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
port := label.GetStringValue(service.SegmentLabels, label.TraefikPort, "")
weight := label.GetIntValue(service.SegmentLabels, label.TraefikWeight, label.DefaultWeightInt)
serverName := "server-" + strconv.Itoa(index)
servers[serverName] = types.Server{
@ -273,127 +182,14 @@ func getServers(service rancherData) map[string]types.Server {
return servers
}
func getWhiteList(service rancherData) *types.WhiteList {
ranges := label.GetSliceStringValue(service.Labels, label.TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: label.GetBoolValue(service.Labels, label.TraefikFrontendWhiteListUseXForwardedFor, false),
func checkSegmentPort(labels map[string]string, segmentName string) (int, error) {
if rawPort, ok := labels[label.TraefikPort]; ok {
port, err := strconv.Atoi(rawPort)
if err != nil {
return port, fmt.Errorf("invalid port value %q for the segment %q: %v", rawPort, segmentName, err)
}
} else {
return 0, fmt.Errorf("port label is missing, please use %s as default value or define port label for all segments ('traefik.<segment_name>.port')", label.TraefikPort)
}
return nil
}
func getRedirect(service rancherData) *types.Redirect {
permanent := label.GetBoolValue(service.Labels, label.TraefikFrontendRedirectPermanent, false)
if label.Has(service.Labels, label.TraefikFrontendRedirectEntryPoint) {
return &types.Redirect{
EntryPoint: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectEntryPoint, ""),
Permanent: permanent,
}
}
if label.Has(service.Labels, label.TraefikFrontendRedirectRegex) &&
label.Has(service.Labels, label.TraefikFrontendRedirectReplacement) {
return &types.Redirect{
Regex: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectRegex, ""),
Replacement: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectReplacement, ""),
Permanent: permanent,
}
}
return nil
}
func getErrorPages(service rancherData) map[string]*types.ErrorPage {
prefix := label.Prefix + label.BaseFrontendErrorPage
return label.ParseErrorPages(service.Labels, prefix, label.RegexpFrontendErrorPage)
}
func getRateLimit(service rancherData) *types.RateLimit {
extractorFunc := label.GetStringValue(service.Labels, label.TraefikFrontendRateLimitExtractorFunc, "")
if len(extractorFunc) == 0 {
return nil
}
prefix := label.Prefix + label.BaseFrontendRateLimit
limits := label.ParseRateSets(service.Labels, prefix, label.RegexpFrontendRateLimit)
return &types.RateLimit{
ExtractorFunc: extractorFunc,
RateSet: limits,
}
}
func getHeaders(service rancherData) *types.Headers {
headers := &types.Headers{
CustomRequestHeaders: label.GetMapValue(service.Labels, label.TraefikFrontendRequestHeaders),
CustomResponseHeaders: label.GetMapValue(service.Labels, label.TraefikFrontendResponseHeaders),
SSLProxyHeaders: label.GetMapValue(service.Labels, label.TraefikFrontendSSLProxyHeaders),
AllowedHosts: label.GetSliceStringValue(service.Labels, label.TraefikFrontendAllowedHosts),
HostsProxyHeaders: label.GetSliceStringValue(service.Labels, label.TraefikFrontendHostsProxyHeaders),
STSSeconds: label.GetInt64Value(service.Labels, label.TraefikFrontendSTSSeconds, 0),
SSLRedirect: label.GetBoolValue(service.Labels, label.TraefikFrontendSSLRedirect, false),
SSLTemporaryRedirect: label.GetBoolValue(service.Labels, label.TraefikFrontendSSLTemporaryRedirect, false),
STSIncludeSubdomains: label.GetBoolValue(service.Labels, label.TraefikFrontendSTSIncludeSubdomains, false),
STSPreload: label.GetBoolValue(service.Labels, label.TraefikFrontendSTSPreload, false),
ForceSTSHeader: label.GetBoolValue(service.Labels, label.TraefikFrontendForceSTSHeader, false),
FrameDeny: label.GetBoolValue(service.Labels, label.TraefikFrontendFrameDeny, false),
ContentTypeNosniff: label.GetBoolValue(service.Labels, label.TraefikFrontendContentTypeNosniff, false),
BrowserXSSFilter: label.GetBoolValue(service.Labels, label.TraefikFrontendBrowserXSSFilter, false),
IsDevelopment: label.GetBoolValue(service.Labels, label.TraefikFrontendIsDevelopment, false),
SSLHost: label.GetStringValue(service.Labels, label.TraefikFrontendSSLHost, ""),
CustomFrameOptionsValue: label.GetStringValue(service.Labels, label.TraefikFrontendCustomFrameOptionsValue, ""),
ContentSecurityPolicy: label.GetStringValue(service.Labels, label.TraefikFrontendContentSecurityPolicy, ""),
PublicKey: label.GetStringValue(service.Labels, label.TraefikFrontendPublicKey, ""),
ReferrerPolicy: label.GetStringValue(service.Labels, label.TraefikFrontendReferrerPolicy, ""),
CustomBrowserXSSValue: label.GetStringValue(service.Labels, label.TraefikFrontendCustomBrowserXSSValue, ""),
}
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
return nil
}
return headers
}
// Label functions
func getFuncString(labelName string, defaultValue string) func(service rancherData) string {
return func(service rancherData) string {
return label.GetStringValue(service.Labels, labelName, defaultValue)
}
}
func getFuncBool(labelName string, defaultValue bool) func(service rancherData) bool {
return func(service rancherData) bool {
return label.GetBoolValue(service.Labels, labelName, defaultValue)
}
}
func getFuncInt(labelName string, defaultValue int) func(service rancherData) int {
return func(service rancherData) int {
return label.GetIntValue(service.Labels, labelName, defaultValue)
}
}
func getFuncInt64(labelName string, defaultValue int64) func(service rancherData) int64 {
return func(service rancherData) int64 {
return label.GetInt64Value(service.Labels, labelName, defaultValue)
}
}
func getFuncSliceString(labelName string) func(service rancherData) []string {
return func(service rancherData) []string {
return label.GetSliceStringValue(service.Labels, labelName)
}
}
func hasFunc(labelName string) func(service rancherData) bool {
return func(service rancherData) bool {
return label.Has(service.Labels, labelName)
}
return 0, nil
}

View file

@ -0,0 +1,12 @@
package rancher
import (
"github.com/containous/traefik/types"
)
func (p *Provider) buildConfiguration(containersInspected []rancherData) *types.Configuration {
if p.TemplateVersion == 1 {
return p.buildConfigurationV1(containersInspected)
}
return p.buildConfigurationV2(containersInspected)
}

View file

@ -251,6 +251,172 @@ func TestProviderBuildConfiguration(t *testing.T) {
},
},
},
{
desc: "when all segment labels are set",
services: []rancherData{
{
Labels: map[string]string{
label.Prefix + "sauternes." + label.SuffixPort: "666",
label.Prefix + "sauternes." + label.SuffixProtocol: "https",
label.Prefix + "sauternes." + label.SuffixWeight: "12",
label.Prefix + "sauternes." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.Prefix + "sauternes." + label.SuffixFrontendEntryPoints: "http,https",
label.Prefix + "sauternes." + label.SuffixFrontendPassHostHeader: "true",
label.Prefix + "sauternes." + label.SuffixFrontendPassTLSCert: "true",
label.Prefix + "sauternes." + label.SuffixFrontendPriority: "666",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectEntryPoint: "https",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectRegex: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true",
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListSourceRange: "10.10.10.10",
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListUseXForwardedFor: "true",
label.Prefix + "sauternes." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + "sauternes." + label.SuffixFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersAllowedHosts: "foo,bar,bor",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersHostsProxyHeaders: "foo,bar,bor",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLHost: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersCustomFrameOptionsValue: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersContentSecurityPolicy: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersPublicKey: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersReferrerPolicy: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersCustomBrowserXSSValue: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSSeconds: "666",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLRedirect: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLTemporaryRedirect: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSIncludeSubdomains: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSPreload: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersForceSTSHeader: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersFrameDeny: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersContentTypeNosniff: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersBrowserXSSFilter: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersIsDevelopment: "true",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foobar",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "foobar",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: "bar_query",
label.Prefix + "sauternes." + label.SuffixFrontendRateLimitExtractorFunc: "client.ip",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9",
},
Health: "healthy",
Containers: []string{"10.0.0.1", "10.0.0.2"},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-sauternes": {
EntryPoints: []string{"http", "https"},
Backend: "backend-sauternes",
Routes: map[string]types.Route{
"route-frontend-sauternes": {
Rule: "Host:.rancher.localhost",
},
},
PassHostHeader: true,
PassTLSCert: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhiteList: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
CustomResponseHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
AllowedHosts: []string{"foo", "bar", "bor"},
HostsProxyHeaders: []string{"foo", "bar", "bor"},
SSLRedirect: true,
SSLTemporaryRedirect: true,
SSLHost: "foo",
SSLProxyHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
STSSeconds: 666,
STSIncludeSubdomains: true,
STSPreload: true,
ForceSTSHeader: true,
FrameDeny: true,
CustomFrameOptionsValue: "foo",
ContentTypeNosniff: true,
BrowserXSSFilter: true,
CustomBrowserXSSValue: "foo",
ContentSecurityPolicy: "foo",
PublicKey: "foo",
ReferrerPolicy: "foo",
IsDevelopment: true,
},
Errors: map[string]*types.ErrorPage{
"bar": {
Status: []string{"500", "600"},
Backend: "foobar",
Query: "bar_query",
},
"foo": {
Status: []string{"404"},
Backend: "foobar",
Query: "foo_query",
},
},
RateLimit: &types.RateLimit{
ExtractorFunc: "client.ip",
RateSet: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
Redirect: &types.Redirect{
EntryPoint: "https",
Regex: "",
Replacement: "",
Permanent: true,
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-sauternes": {
Servers: map[string]types.Server{
"server-0": {
URL: "https://10.0.0.1:666",
Weight: 12,
},
"server-1": {
URL: "https://10.0.0.2:666",
Weight: 12,
},
},
},
},
},
{
desc: "with services",
services: []rancherData{
@ -298,7 +464,6 @@ func TestProviderBuildConfiguration(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -634,298 +799,15 @@ func TestGetBackendName(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
segmentProperties := label.ExtractTraefikLabels(test.service.Labels)
test.service.SegmentLabels = segmentProperties[""]
actual := getBackendName(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetCircuitBreaker(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.CircuitBreaker
}{
{
desc: "should return nil when no CB label",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when CB label is set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendCircuitBreakerExpression: "NetworkErrorRatio() > 0.5",
},
Health: "healthy",
State: "active",
},
expected: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getCircuitBreaker(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetLoadBalancer(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.LoadBalancer
}{
{
desc: "should return nil when no LB labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when labels are set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendLoadBalancerMethod: "drr",
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickiness: "true",
label.TraefikBackendLoadBalancerStickinessCookieName: "foo",
},
Health: "healthy",
State: "active",
},
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "foo",
},
},
},
{
desc: "should return a nil Stickiness when Stickiness is not set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendLoadBalancerMethod: "drr",
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickinessCookieName: "foo",
},
Health: "healthy",
State: "active",
},
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: nil,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getLoadBalancer(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetMaxConn(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.MaxConn
}{
{
desc: "should return nil when no max conn labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return nil when no amount label",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return default when no empty extractorFunc label",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendMaxConnExtractorFunc: "",
label.TraefikBackendMaxConnAmount: "666",
},
Health: "healthy",
State: "active",
},
expected: &types.MaxConn{
ExtractorFunc: "request.host",
Amount: 666,
},
},
{
desc: "should return a struct when max conn labels are set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
label.TraefikBackendMaxConnAmount: "666",
},
Health: "healthy",
State: "active",
},
expected: &types.MaxConn{
ExtractorFunc: "client.ip",
Amount: 666,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getMaxConn(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetHealthCheck(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.HealthCheck
}{
{
desc: "should return nil when no health check labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return nil when no health check Path label",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendHealthCheckPort: "80",
label.TraefikBackendHealthCheckInterval: "6",
},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when health check labels are set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendHealthCheckPath: "/health",
label.TraefikBackendHealthCheckPort: "80",
label.TraefikBackendHealthCheckInterval: "6",
},
Health: "healthy",
State: "active",
},
expected: &types.HealthCheck{
Path: "/health",
Port: 80,
Interval: "6",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getHealthCheck(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetBuffering(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.Buffering
}{
{
desc: "should return nil when no buffering labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when buffering labels are set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760",
label.TraefikBackendBufferingMemResponseBodyBytes: "2097152",
label.TraefikBackendBufferingMaxRequestBodyBytes: "10485760",
label.TraefikBackendBufferingMemRequestBodyBytes: "2097152",
label.TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2",
},
Health: "healthy",
State: "active",
},
expected: &types.Buffering{
MaxResponseBodyBytes: 10485760,
MemResponseBodyBytes: 2097152,
MaxRequestBodyBytes: 10485760,
MemRequestBodyBytes: 2097152,
RetryExpression: "IsNetworkError() && Attempts() <= 2",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getBuffering(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetServers(t *testing.T) {
testCases := []struct {
desc string
@ -998,352 +880,10 @@ func TestGetServers(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
segmentProperties := label.ExtractTraefikLabels(test.service.Labels)
test.service.SegmentLabels = segmentProperties[""]
actual := getServers(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when only range",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
},
Health: "healthy",
State: "active",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
Health: "healthy",
State: "active",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
Health: "healthy",
State: "active",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getWhiteList(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRedirect(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.Redirect
}{
{
desc: "should return nil when no redirect labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should use only entry point tag when mix regex redirect and entry point redirect",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label (permanent)",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectPermanent: "true",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
EntryPoint: "https",
Permanent: true,
},
},
{
desc: "should return a struct when regex redirect labels",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
},
},
{
desc: "should return a struct when regex redirect labels (permanent)",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
label.TraefikFrontendRedirectPermanent: "true",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
Permanent: true,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getRedirect(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRateLimit(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.RateLimit
}{
{
desc: "should return nil when no rate limit labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when rate limit labels are defined",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRateLimitExtractorFunc: "client.ip",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9",
},
Health: "healthy",
State: "active",
},
expected: &types.RateLimit{
ExtractorFunc: "client.ip",
RateSet: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
},
{
desc: "should return nil when ExtractorFunc is missing",
service: rancherData{
Labels: map[string]string{
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9",
},
Health: "healthy",
State: "active",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getRateLimit(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetHeaders(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.Headers
}{
{
desc: "should return nil when no custom headers options are set",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when all custom headers options are set",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.TraefikFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.TraefikFrontendSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.TraefikFrontendAllowedHosts: "foo,bar,bor",
label.TraefikFrontendHostsProxyHeaders: "foo,bar,bor",
label.TraefikFrontendSSLHost: "foo",
label.TraefikFrontendCustomFrameOptionsValue: "foo",
label.TraefikFrontendContentSecurityPolicy: "foo",
label.TraefikFrontendPublicKey: "foo",
label.TraefikFrontendReferrerPolicy: "foo",
label.TraefikFrontendCustomBrowserXSSValue: "foo",
label.TraefikFrontendSTSSeconds: "666",
label.TraefikFrontendSSLRedirect: "true",
label.TraefikFrontendSSLTemporaryRedirect: "true",
label.TraefikFrontendSTSIncludeSubdomains: "true",
label.TraefikFrontendSTSPreload: "true",
label.TraefikFrontendForceSTSHeader: "true",
label.TraefikFrontendFrameDeny: "true",
label.TraefikFrontendContentTypeNosniff: "true",
label.TraefikFrontendBrowserXSSFilter: "true",
label.TraefikFrontendIsDevelopment: "true",
},
Health: "healthy",
State: "active",
},
expected: &types.Headers{
CustomRequestHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
CustomResponseHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
SSLProxyHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
AllowedHosts: []string{"foo", "bar", "bor"},
HostsProxyHeaders: []string{"foo", "bar", "bor"},
SSLHost: "foo",
CustomFrameOptionsValue: "foo",
ContentSecurityPolicy: "foo",
PublicKey: "foo",
ReferrerPolicy: "foo",
CustomBrowserXSSValue: "foo",
STSSeconds: 666,
SSLRedirect: true,
SSLTemporaryRedirect: true,
STSIncludeSubdomains: true,
STSPreload: true,
ForceSTSHeader: true,
FrameDeny: true,
ContentTypeNosniff: true,
BrowserXSSFilter: true,
IsDevelopment: true,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getHeaders(test.service)
assert.Equal(t, test.expected, actual)
})
}

View file

@ -0,0 +1,226 @@
package rancher
import (
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
)
func (p *Provider) buildConfigurationV1(services []rancherData) *types.Configuration {
var RancherFuncMap = template.FuncMap{
"getDomain": getFuncStringV1(label.TraefikDomain, p.Domain),
// Backend functions
"getPort": getFuncStringV1(label.TraefikPort, ""),
"getProtocol": getFuncStringV1(label.TraefikProtocol, label.DefaultProtocol),
"getWeight": getFuncIntV1(label.TraefikWeight, label.DefaultWeightInt),
"hasCircuitBreakerLabel": hasFuncV1(label.TraefikBackendCircuitBreakerExpression),
"getCircuitBreakerExpression": getFuncStringV1(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
"hasLoadBalancerLabel": hasLoadBalancerLabel,
"getLoadBalancerMethod": getFuncStringV1(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
"hasMaxConnLabels": hasMaxConnLabels,
"getMaxConnAmount": getFuncInt64V1(label.TraefikBackendMaxConnAmount, 0),
"getMaxConnExtractorFunc": getFuncStringV1(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
"getSticky": getStickyV1,
"hasStickinessLabel": hasFuncV1(label.TraefikBackendLoadBalancerStickiness),
"getStickinessCookieName": getFuncStringV1(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
// Frontend functions
"getBackend": getBackendNameV1,
"getFrontendRule": p.getFrontendRule,
"getPriority": getFuncIntV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getEntryPoints": getFuncSliceStringV1(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringV1(label.TraefikFrontendAuthBasic),
"hasRedirect": hasRedirect,
"getRedirectEntryPoint": getRedirectEntryPoint,
"getRedirectRegex": getRedirectRegex,
"getRedirectReplacement": getRedirectReplacement,
}
// filter services
filteredServices := fun.Filter(p.serviceFilterV1, services).([]rancherData)
frontends := map[string]rancherData{}
backends := map[string]rancherData{}
for _, service := range filteredServices {
frontendName := p.getFrontendNameV1(service)
frontends[frontendName] = service
backendName := getBackendNameV1(service)
backends[backendName] = service
}
templateObjects := struct {
Frontends map[string]rancherData
Backends map[string]rancherData
Domain string
}{
Frontends: frontends,
Backends: backends,
Domain: p.Domain,
}
configuration, err := p.GetConfiguration("templates/rancher-v1.tmpl", RancherFuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration
}
// Deprecated
func (p *Provider) serviceFilterV1(service rancherData) bool {
if service.Labels[label.TraefikPort] == "" {
log.Debugf("Filtering service %s without traefik.port label", service.Name)
return false
}
if !label.IsEnabled(service.Labels, p.ExposedByDefault) {
log.Debugf("Filtering disabled service %s", service.Name)
return false
}
constraintTags := label.GetSliceStringValue(service.Labels, label.TraefikTags)
if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil {
log.Debugf("Filtering service %s with constraint %s", service.Name, failingConstraint.String())
}
return false
}
// Only filter services by Health (HealthState) and State if EnableServiceHealthFilter is true
if p.EnableServiceHealthFilter {
if service.Health != "" && service.Health != healthy && service.Health != updatingHealthy {
log.Debugf("Filtering service %s with healthState of %s", service.Name, service.Health)
return false
}
if service.State != "" && service.State != active && service.State != updatingActive && service.State != upgraded && service.State != upgrading {
log.Debugf("Filtering service %s with state of %s", service.Name, service.State)
return false
}
}
return true
}
// Deprecated
func (p *Provider) getFrontendNameV1(service rancherData) string {
return provider.Normalize(p.getFrontendRule(service))
}
// Deprecated
func getBackendNameV1(service rancherData) string {
backend := label.GetStringValue(service.Labels, label.TraefikBackend, service.Name)
return provider.Normalize(backend)
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getStickyV1(service rancherData) bool {
if label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
}
return label.GetBoolValue(service.Labels, label.TraefikBackendLoadBalancerSticky, false)
}
// Deprecated
func hasLoadBalancerLabel(service rancherData) bool {
method := label.Has(service.Labels, label.TraefikBackendLoadBalancerMethod)
sticky := label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky)
stickiness := label.Has(service.Labels, label.TraefikBackendLoadBalancerStickiness)
cookieName := label.Has(service.Labels, label.TraefikBackendLoadBalancerStickinessCookieName)
return method || sticky || stickiness || cookieName
}
// Deprecated
func hasMaxConnLabels(service rancherData) bool {
mca := label.Has(service.Labels, label.TraefikBackendMaxConnAmount)
mcef := label.Has(service.Labels, label.TraefikBackendMaxConnExtractorFunc)
return mca && mcef
}
func hasRedirect(service rancherData) bool {
value := label.GetStringValue(service.Labels, label.TraefikFrontendRedirectEntryPoint, "")
frep := len(value) > 0
value = label.GetStringValue(service.Labels, label.TraefikFrontendRedirectRegex, "")
frrg := len(value) > 0
value = label.GetStringValue(service.Labels, label.TraefikFrontendRedirectReplacement, "")
frrp := len(value) > 0
return frep || frrg && frrp
}
func getRedirectEntryPoint(service rancherData) string {
value := label.GetStringValue(service.Labels, label.TraefikFrontendRedirectEntryPoint, "")
if len(value) == 0 {
return ""
}
return value
}
func getRedirectRegex(service rancherData) string {
value := label.GetStringValue(service.Labels, label.TraefikFrontendRedirectRegex, "")
if len(value) == 0 {
return ""
}
return value
}
func getRedirectReplacement(service rancherData) string {
value := label.GetStringValue(service.Labels, label.TraefikFrontendRedirectReplacement, "")
if len(value) == 0 {
return ""
}
return value
}
// Label functions
// Deprecated
func getFuncStringV1(labelName string, defaultValue string) func(service rancherData) string {
return func(service rancherData) string {
return label.GetStringValue(service.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncBoolV1(labelName string, defaultValue bool) func(service rancherData) bool {
return func(service rancherData) bool {
return label.GetBoolValue(service.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncIntV1(labelName string, defaultValue int) func(service rancherData) int {
return func(service rancherData) int {
return label.GetIntValue(service.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncInt64V1(labelName string, defaultValue int64) func(service rancherData) int64 {
return func(service rancherData) int64 {
return label.GetInt64Value(service.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncSliceStringV1(labelName string) func(service rancherData) []string {
return func(service rancherData) []string {
return label.GetSliceStringValue(service.Labels, labelName)
}
}
// Deprecated
func hasFuncV1(labelName string) func(service rancherData) bool {
return func(service rancherData) bool {
return label.Has(service.Labels, labelName)
}
}

View file

@ -0,0 +1,503 @@
package rancher
import (
"testing"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProviderBuildConfigurationV1(t *testing.T) {
provider := &Provider{
Domain: "rancher.localhost",
ExposedByDefault: true,
}
testCases := []struct {
desc string
services []rancherData
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "without services",
services: []rancherData{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
desc: "when all labels are set",
services: []rancherData{
{
Labels: map[string]string{
label.TraefikPort: "666",
label.TraefikProtocol: "https",
label.TraefikWeight: "12",
label.TraefikBackend: "foobar",
label.TraefikBackendCircuitBreakerExpression: "NetworkErrorRatio() > 0.5",
label.TraefikBackendLoadBalancerMethod: "drr",
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickiness: "true",
label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate",
label.TraefikBackendMaxConnAmount: "666",
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendPassHostHeader: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRule: "Host:traefik.io",
},
Health: "healthy",
Containers: []string{"10.0.0.1", "10.0.0.2"},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-traefik-io": {
EntryPoints: []string{
"http",
"https",
},
Backend: "backend-foobar",
Routes: map[string]types.Route{
"route-frontend-Host-traefik-io": {
Rule: "Host:traefik.io",
},
},
PassHostHeader: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
Redirect: &types.Redirect{
EntryPoint: "https",
Regex: "nope",
Replacement: "nope",
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"server-0": {
URL: "https://10.0.0.1:666",
Weight: 12,
},
"server-1": {
URL: "https://10.0.0.2:666",
Weight: 12,
},
},
CircuitBreaker: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
LoadBalancer: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
},
},
MaxConn: &types.MaxConn{
Amount: 666,
ExtractorFunc: "client.ip",
},
},
},
},
{
desc: "with services",
services: []rancherData{
{
Name: "test/service",
Labels: map[string]string{
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"},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test-service-rancher-localhost": {
Backend: "backend-test-service",
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Priority: 0,
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"route-frontend-Host-test-service-rancher-localhost": {
Rule: "Host:test.service.rancher.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test-service": {
Servers: map[string]types.Server{
"server-0": {
URL: "http://127.0.0.1:80",
Weight: 0,
},
},
CircuitBreaker: nil,
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actualConfig := provider.buildConfigurationV1(test.services)
require.NotNil(t, actualConfig)
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestProviderServiceFilterV1(t *testing.T) {
provider := &Provider{
Domain: "rancher.localhost",
EnableServiceHealthFilter: true,
}
constraint, _ := types.NewConstraint("tag==ch*se")
provider.Constraints = types.Constraints{constraint}
testCases := []struct {
desc string
service rancherData
expected bool
}{
{
desc: "missing Port labels, don't respect constraint",
service: rancherData{
Labels: map[string]string{
label.TraefikEnable: "true",
},
Health: "healthy",
State: "active",
},
expected: false,
},
{
desc: "don't respect constraint",
service: rancherData{
Labels: map[string]string{
label.TraefikPort: "80",
label.TraefikEnable: "false",
},
Health: "healthy",
State: "active",
},
expected: false,
},
{
desc: "unhealthy",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "cheese",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "unhealthy",
State: "active",
},
expected: false,
},
{
desc: "inactive",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "not-cheesy",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "healthy",
State: "inactive",
},
expected: false,
},
{
desc: "healthy & active, tag: cheese",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "cheese",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "healthy",
State: "active",
},
expected: true,
},
{
desc: "healthy & active, tag: chose",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "chose",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "healthy",
State: "active",
},
expected: true,
},
{
desc: "healthy & upgraded",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "cheeeeese",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "healthy",
State: "upgraded",
},
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.serviceFilterV1(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestContainerFilterV1(t *testing.T) {
testCases := []struct {
name string
healthState string
state string
expected bool
}{
{
healthState: "unhealthy",
state: "running",
expected: false,
},
{
healthState: "healthy",
state: "stopped",
expected: false,
},
{
state: "stopped",
expected: false,
},
{
healthState: "healthy",
state: "running",
expected: true,
},
{
healthState: "updating-healthy",
state: "updating-running",
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.healthState+" "+test.state, func(t *testing.T) {
t.Parallel()
actual := containerFilter(test.name, test.healthState, test.state)
assert.Equal(t, test.expected, actual)
})
}
}
func TestProviderGetFrontendNameV1(t *testing.T) {
provider := &Provider{Domain: "rancher.localhost"}
testCases := []struct {
desc string
service rancherData
expected string
}{
{
desc: "default",
service: rancherData{
Name: "foo",
},
expected: "Host-foo-rancher-localhost",
},
{
desc: "with Headers label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Headers:User-Agent,bat/0.1.0",
},
},
expected: "Headers-User-Agent-bat-0-1-0",
},
{
desc: "with Host label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
},
},
expected: "Host-foo-bar",
},
{
desc: "with Path label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Path:/test",
},
},
expected: "Path-test",
},
{
desc: "with PathPrefix label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "PathPrefix:/test2",
},
},
expected: "PathPrefix-test2",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.getFrontendNameV1(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestProviderGetFrontendRuleV1(t *testing.T) {
provider := &Provider{Domain: "rancher.localhost"}
testCases := []struct {
desc string
service rancherData
expected string
}{
{
desc: "host",
service: rancherData{
Name: "foo",
},
expected: "Host:foo.rancher.localhost",
},
{
desc: "host with /",
service: rancherData{
Name: "foo/bar",
},
expected: "Host:foo.bar.rancher.localhost",
},
{
desc: "with Host label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Host:foo.bar.com",
},
},
expected: "Host:foo.bar.com",
},
{
desc: "with Path label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Path:/test",
},
},
expected: "Path:/test",
},
{
desc: "with PathPrefix label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "PathPrefix:/test2",
},
},
expected: "PathPrefix:/test2",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.getFrontendRule(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetBackendNameV1(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected string
}{
{
desc: "without label",
service: rancherData{
Name: "test-service",
},
expected: "test-service",
},
{
desc: "with label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikBackend: "foobar",
},
},
expected: "foobar",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getBackendNameV1(test.service)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -38,11 +38,13 @@ type Provider struct {
}
type rancherData struct {
Name string
Labels map[string]string // List of labels set to container or service
Containers []string
Health string
State string
Name string
Labels map[string]string // List of labels set to container or service
Containers []string
Health string
State string
SegmentLabels map[string]string
SegmentName string
}
func (r rancherData) String() string {