package servicefabric import ( "encoding/json" "errors" "math" "strings" "text/template" "github.com/containous/traefik/log" "github.com/containous/traefik/provider" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/types" sf "github.com/jjcollinge/servicefabric" ) func (p *Provider) buildConfiguration(sfClient sfClient) (*types.Configuration, error) { var sfFuncMap = template.FuncMap{ // Services "getServices": getServices, "hasLabel": hasService, "getLabelValue": getServiceStringLabel, "getLabelsWithPrefix": getServiceLabelsWithPrefix, "isPrimary": isPrimary, "isStateful": isStateful, "isStateless": isStateless, "isEnabled": getFuncBoolLabel(label.TraefikEnable, false), "getBackendName": getBackendName, "getDefaultEndpoint": getDefaultEndpoint, "getNamedEndpoint": getNamedEndpoint, // FIXME unused "getApplicationParameter": getApplicationParameter, // FIXME unused "doesAppParamContain": doesAppParamContain, // FIXME unused "filterServicesByLabelValue": filterServicesByLabelValue, // FIXME unused // Backend functions "getWeight": getFuncServiceStringLabel(label.TraefikWeight, label.DefaultWeight), "getProtocol": getFuncServiceStringLabel(label.TraefikProtocol, label.DefaultProtocol), "getMaxConn": getMaxConn, "getHealthCheck": getHealthCheck, "getCircuitBreaker": getCircuitBreaker, "getLoadBalancer": getLoadBalancer, // Frontend Functions "getPriority": getFuncServiceStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority), "getPassHostHeader": getFuncServiceStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader), "getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, false), "getEntryPoints": getFuncServiceSliceStringLabel(label.TraefikFrontendEntryPoints), "getBasicAuth": getFuncServiceSliceStringLabel(label.TraefikFrontendAuthBasic), "getWhitelistSourceRange": getFuncServiceSliceStringLabel(label.TraefikFrontendWhitelistSourceRange), "getFrontendRules": getFuncServiceLabelWithPrefix(label.TraefikFrontendRule), "getHeaders": getHeaders, "getRedirect": getRedirect, // SF Service Grouping "getGroupedServices": getFuncServicesGroupedByLabel(traefikSFGroupName), "getGroupedWeight": getFuncServiceStringLabel(traefikSFGroupWeight, "1"), } services, err := getClusterServices(sfClient) if err != nil { return nil, err } templateObjects := struct { Services []ServiceItemExtended }{ Services: services, } return p.GetConfiguration(tmpl, sfFuncMap, templateObjects) } func isStateful(service ServiceItemExtended) bool { return service.ServiceKind == "Stateful" } func isStateless(service ServiceItemExtended) bool { return service.ServiceKind == "Stateless" } func getBackendName(service ServiceItemExtended, partition PartitionItemExtended) string { return provider.Normalize(service.Name + partition.PartitionInformation.ID) } func getDefaultEndpoint(instance replicaInstance) string { id, data := instance.GetReplicaData() endpoint, err := getReplicaDefaultEndpoint(data) if err != nil { log.Warnf("No default endpoint for replica %s in service %s endpointData: %s", id, data.Address) return "" } return endpoint } func getReplicaDefaultEndpoint(replicaData *sf.ReplicaItemBase) (string, error) { endpoints, err := decodeEndpointData(replicaData.Address) if err != nil { return "", err } var defaultHTTPEndpoint string for _, v := range endpoints { if strings.Contains(v, "http") { defaultHTTPEndpoint = v break } } if len(defaultHTTPEndpoint) == 0 { return "", errors.New("no default endpoint found") } return defaultHTTPEndpoint, nil } func getNamedEndpoint(instance replicaInstance, endpointName string) string { id, data := instance.GetReplicaData() endpoint, err := getReplicaNamedEndpoint(data, endpointName) if err != nil { log.Warnf("No names endpoint of %s for replica %s in endpointData: %s. Error: %v", endpointName, id, data.Address, err) return "" } return endpoint } func getReplicaNamedEndpoint(replicaData *sf.ReplicaItemBase, endpointName string) (string, error) { endpoints, err := decodeEndpointData(replicaData.Address) if err != nil { return "", err } endpoint, exists := endpoints[endpointName] if !exists { return "", errors.New("endpoint doesn't exist") } return endpoint, nil } func getApplicationParameter(app sf.ApplicationItem, key string) string { for _, param := range app.Parameters { if param.Key == key { return param.Value } } log.Errorf("Parameter %s doesn't exist in app %s", key, app.Name) return "" } func getServices(services []ServiceItemExtended, key string) map[string][]ServiceItemExtended { result := map[string][]ServiceItemExtended{} for _, service := range services { if value, exists := service.Labels[key]; exists { if matchingServices, hasKeyAlready := result[value]; hasKeyAlready { result[value] = append(matchingServices, service) } else { result[value] = []ServiceItemExtended{service} } } } return result } func doesAppParamContain(app sf.ApplicationItem, key, shouldContain string) bool { value := getApplicationParameter(app, key) return strings.Contains(value, shouldContain) } func filterServicesByLabelValue(services []ServiceItemExtended, key, expectedValue string) []ServiceItemExtended { var srvWithLabel []ServiceItemExtended for _, service := range services { value, exists := service.Labels[key] if exists && value == expectedValue { srvWithLabel = append(srvWithLabel, service) } } return srvWithLabel } func decodeEndpointData(endpointData string) (map[string]string, error) { var endpointsMap map[string]map[string]string if endpointData == "" { return nil, errors.New("endpoint data is empty") } err := json.Unmarshal([]byte(endpointData), &endpointsMap) if err != nil { return nil, err } endpoints, endpointsExist := endpointsMap["Endpoints"] if !endpointsExist { return nil, errors.New("endpoint doesn't exist in endpoint data") } return endpoints, nil } func getHeaders(service ServiceItemExtended) *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 } func getRedirect(service ServiceItemExtended) *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 getMaxConn(service ServiceItemExtended) *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 ServiceItemExtended) *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 getCircuitBreaker(service ServiceItemExtended) *types.CircuitBreaker { circuitBreaker := label.GetStringValue(service.Labels, label.TraefikBackendCircuitBreakerExpression, "") if len(circuitBreaker) == 0 { return nil } return &types.CircuitBreaker{Expression: circuitBreaker} } func getLoadBalancer(service ServiceItemExtended) *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 } // TODO: Deprecated // replaced by Stickiness // Deprecated func getSticky(service ServiceItemExtended) 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) }