Update Service Fabric backend.

This commit is contained in:
Ludovic Fernandez 2018-03-22 17:42:03 +01:00 committed by Traefiker Bot
parent 1b410980ca
commit 0fa0c2256a
10 changed files with 677 additions and 352 deletions

View file

@ -1,59 +0,0 @@
package servicefabric
import (
"strconv"
"strings"
)
func getFuncBoolLabel(labelName string, defaultValue bool) func(service ServiceItemExtended) bool {
return func(service ServiceItemExtended) bool {
return getBoolValue(service.Labels, labelName, defaultValue)
}
}
func getFuncServiceStringLabel(service ServiceItemExtended, labelName string, defaultValue string) string {
return getStringValue(service.Labels, labelName, defaultValue)
}
func hasFuncService(service ServiceItemExtended, labelName string) bool {
return hasLabel(service.Labels, labelName)
}
func getServiceLabelsWithPrefix(service ServiceItemExtended, prefix string) map[string]string {
results := make(map[string]string)
for k, v := range service.Labels {
if strings.HasPrefix(k, prefix) {
results[k] = v
}
}
return results
}
// must be replace by label.Has()
// Deprecated
func hasLabel(labels map[string]string, labelName string) bool {
value, ok := labels[labelName]
return ok && len(value) > 0
}
// must be replace by label.GetStringValue()
// Deprecated
func getStringValue(labels map[string]string, labelName string, defaultValue string) string {
if value, ok := labels[labelName]; ok && len(value) > 0 {
return value
}
return defaultValue
}
// must be replace by label.GetBoolValue()
// Deprecated
func getBoolValue(labels map[string]string, labelName string, defaultValue bool) bool {
rawValue, ok := labels[labelName]
if ok {
v, err := strconv.ParseBool(rawValue)
if err == nil {
return v
}
}
return defaultValue
}

View file

@ -1,17 +1,14 @@
package servicefabric
import (
"encoding/json"
"errors"
"net/http"
"strings"
"text/template"
"time"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
sf "github.com/jjcollinge/servicefabric"
@ -19,7 +16,7 @@ import (
var _ provider.Provider = (*Provider)(nil)
const traefikLabelPrefix = "traefik"
const traefikServiceFabricExtensionKey = "Traefik"
// Provider holds for configuration for the provider
type Provider struct {
@ -93,104 +90,6 @@ func (p *Provider) updateConfig(configurationChan chan<- types.ConfigMessage, po
return nil
}
func (p *Provider) buildConfiguration(sfClient sfClient) (*types.Configuration, error) {
var sfFuncMap = template.FuncMap{
"getServices": getServices,
"hasLabel": hasFuncService,
"getLabelValue": getFuncServiceStringLabel,
"getLabelsWithPrefix": getServiceLabelsWithPrefix,
"isPrimary": isPrimary,
"isExposed": getFuncBoolLabel("expose", false),
"getBackendName": getBackendName,
"getDefaultEndpoint": getDefaultEndpoint,
"getNamedEndpoint": getNamedEndpoint, // FIXME unused
"getApplicationParameter": getApplicationParameter, // FIXME unused
"doesAppParamContain": doesAppParamContain, // FIXME unused
"filterServicesByLabelValue": filterServicesByLabelValue, // FIXME unused
}
services, err := getClusterServices(sfClient)
if err != nil {
return nil, err
}
templateObjects := struct {
Services []ServiceItemExtended
}{
Services: services,
}
return p.GetConfiguration(tmpl, sfFuncMap, templateObjects)
}
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 doesAppParamContain(app sf.ApplicationItem, key, shouldContain string) bool {
value := getApplicationParameter(app, key)
return strings.Contains(value, shouldContain)
}
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 getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) {
apps, err := sfClient.GetApplications()
if err != nil {
@ -210,7 +109,7 @@ func getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) {
Application: app,
}
if labels, err := sfClient.GetServiceLabels(&service, &app, traefikLabelPrefix); err != nil {
if labels, err := getLabels(sfClient, &service, &app); err != nil {
log.Error(err)
} else {
item.Labels = labels
@ -222,9 +121,9 @@ func getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) {
for _, partition := range partitions.Items {
partitionExt := PartitionItemExtended{PartitionItem: partition}
if partition.ServiceKind == "Stateful" {
if isStateful(item) {
partitionExt.Replicas = getValidReplicas(sfClient, app, service, partition)
} else if partition.ServiceKind == "Stateless" {
} else if isStateless(item) {
partitionExt.Instances = getValidInstances(sfClient, app, service, partition)
} else {
log.Errorf("Unsupported service kind %s in service %s", partition.ServiceKind, service.Name)
@ -272,31 +171,6 @@ func getValidInstances(sfClient sfClient, app sf.ApplicationItem, service sf.Ser
return validInstances
}
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 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 isPrimary(instance replicaInstance) bool {
_, data := instance.GetReplicaData()
return data.ReplicaRole == "Primary"
@ -311,26 +185,21 @@ func hasHTTPEndpoint(instanceData *sf.ReplicaItemBase) bool {
return err == nil
}
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)
// Return a set of labels from the Extension and Property manager
// Allow Extension labels to disable importing labels from the property manager.
func getLabels(sfClient sfClient, service *sf.ServiceItem, app *sf.ApplicationItem) (map[string]string, error) {
labels, err := sfClient.GetServiceExtensionMap(service, app, traefikServiceFabricExtensionKey)
if err != nil {
log.Errorf("Error retrieving serviceExtensionMap: %v", err)
return nil, err
}
endpoints, endpointsExist := endpointsMap["Endpoints"]
if !endpointsExist {
return nil, errors.New("endpoint doesn't exist in endpoint data")
if label.GetBoolValue(labels, traefikSFEnableLabelOverrides, traefikSFEnableLabelOverridesDefault) {
if exists, properties, err := sfClient.GetProperties(service.ID); err == nil && exists {
for key, value := range properties {
labels[key] = value
}
}
}
return endpoints, nil
}
func getBackendName(service ServiceItemExtended, partition PartitionItemExtended) string {
return provider.Normalize(service.Name + partition.PartitionInformation.ID)
return labels, nil
}

View file

@ -0,0 +1,321 @@
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)
}

View file

@ -0,0 +1,63 @@
package servicefabric
import (
"strings"
"github.com/containous/traefik/provider/label"
)
// SF Specific Traefik Labels
const (
traefikSFGroupName = "traefik.servicefabric.groupname"
traefikSFGroupWeight = "traefik.servicefabric.groupweight"
traefikSFEnableLabelOverrides = "traefik.servicefabric.enablelabeloverrides"
traefikSFEnableLabelOverridesDefault = true
)
func getFuncBoolLabel(labelName string, defaultValue bool) func(service ServiceItemExtended) bool {
return func(service ServiceItemExtended) bool {
return label.GetBoolValue(service.Labels, labelName, defaultValue)
}
}
func getServiceStringLabel(service ServiceItemExtended, labelName string, defaultValue string) string {
return label.GetStringValue(service.Labels, labelName, defaultValue)
}
func getFuncServiceStringLabel(labelName string, defaultValue string) func(service ServiceItemExtended) string {
return func(service ServiceItemExtended) string {
return label.GetStringValue(service.Labels, labelName, defaultValue)
}
}
func getFuncServiceSliceStringLabel(labelName string) func(service ServiceItemExtended) []string {
return func(service ServiceItemExtended) []string {
return label.GetSliceStringValue(service.Labels, labelName)
}
}
func hasService(service ServiceItemExtended, labelName string) bool {
return label.Has(service.Labels, labelName)
}
func getFuncServiceLabelWithPrefix(labelName string) func(service ServiceItemExtended) map[string]string {
return func(service ServiceItemExtended) map[string]string {
return getServiceLabelsWithPrefix(service, labelName)
}
}
func getFuncServicesGroupedByLabel(labelName string) func(services []ServiceItemExtended) map[string][]ServiceItemExtended {
return func(services []ServiceItemExtended) map[string][]ServiceItemExtended {
return getServices(services, labelName)
}
}
func getServiceLabelsWithPrefix(service ServiceItemExtended, prefix string) map[string]string {
results := make(map[string]string)
for k, v := range service.Labels {
if strings.HasPrefix(k, prefix) {
results[k] = v
}
}
return results
}

View file

@ -1,140 +1,209 @@
package servicefabric
const tmpl = `
{{$groupedServiceMap := getServices .Services "backend.group.name"}}
[backends]
{{range $aggName, $aggServices := $groupedServiceMap }}
[backends."{{$aggName}}"]
{{range $service := $aggServices}}
{{range $partition := $service.Partitions}}
{{range $instance := $partition.Instances}}
[backends."{{$aggName}}".servers."{{$service.ID}}-{{$instance.ID}}"]
url = "{{getDefaultEndpoint $instance}}"
weight = {{getLabelValue $service "backend.group.weight" "1"}}
{{end}}
{{end}}
{{end}}
{{end}}
{{range $service := .Services}}
{{range $partition := $service.Partitions}}
{{if eq $partition.ServiceKind "Stateless"}}
[backends."{{$service.Name}}"]
[backends."{{$service.Name}}".LoadBalancer]
{{if hasLabel $service "backend.loadbalancer.method"}}
method = "{{getLabelValue $service "backend.loadbalancer.method" "" }}"
{{else}}
method = "drr"
{{range $aggName, $aggServices := getGroupedServices .Services }}
[backends."{{ $aggName }}"]
{{range $service := $aggServices }}
{{range $partition := $service.Partitions }}
{{range $instance := $partition.Instances }}
[backends."{{ $aggName }}".servers."{{ $service.ID }}-{{ $instance.ID }}"]
url = "{{ getDefaultEndpoint $instance }}"
weight = {{ getGroupedWeight $service }}
{{end}}
{{end}}
{{end}}
{{end}}
{{range $service := .Services }}
{{if isEnabled $service }}
{{range $partition := $service.Partitions }}
{{if isStateless $service }}
{{ $backendName := $service.Name }}
[backends."{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $service }}
{{if $circuitBreaker }}
[backends."{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
{{if hasLabel $service "backend.healthcheck"}}
[backends."{{$service.Name}}".healthcheck]
path = "{{getLabelValue $service "backend.healthcheck" ""}}"
interval = "{{getLabelValue $service "backend.healthcheck.interval" "10s"}}"
{{ $loadBalancer := getLoadBalancer $service }}
{{if $loadBalancer }}
[backends."{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
sticky = {{ $loadBalancer.Sticky }}
{{if $loadBalancer.Stickiness }}
[backends."{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
{{end}}
{{end}}
{{if hasLabel $service "backend.loadbalancer.stickiness"}}
[backends."{{$service.Name}}".LoadBalancer.stickiness]
{{ $maxConn := getMaxConn $service }}
{{if $maxConn }}
[backends."{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
{{if hasLabel $service "backend.circuitbreaker"}}
[backends."{{$service.Name}}".circuitbreaker]
expression = "{{getLabelValue $service "backend.circuitbreaker" ""}}"
{{end}}
{{if hasLabel $service "backend.maxconn.amount"}}
[backends."{{$service.Name}}".maxconn]
amount = {{getLabelValue $service "backend.maxconn.amount" ""}}
{{if hasLabel $service "backend.maxconn.extractorfunc"}}
extractorfunc = "{{getLabelValue $service "backend.maxconn.extractorfunc" ""}}"
{{end}}
{{ $healthCheck := getHealthCheck $service }}
{{if $healthCheck }}
[backends."{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
port = {{ $healthCheck.Port }}
interval = "{{ $healthCheck.Interval }}"
{{end}}
{{range $instance := $partition.Instances}}
[backends."{{$service.Name}}".servers."{{$instance.ID}}"]
url = "{{getDefaultEndpoint $instance}}"
weight = {{getLabelValue $service "backend.weight" "1"}}
[backends."{{ $service.Name }}".servers."{{ $instance.ID }}"]
url = "{{ getDefaultEndpoint $instance }}"
weight = {{ getLabelValue $service "backend.weight" "1" }}
{{end}}
{{else if eq $partition.ServiceKind "Stateful"}}
{{else if isStateful $service}}
{{range $replica := $partition.Replicas}}
{{if isPrimary $replica}}
{{$backendName := getBackendName $service.Name $partition}}
[backends."{{$backendName}}".servers."{{$replica.ID}}"]
url = "{{getDefaultEndpoint $replica}}"
weight = 1
{{ $backendName := getBackendName $service $partition }}
[backends."{{ $backendName }}".servers."{{ $replica.ID }}"]
url = "{{ getDefaultEndpoint $replica }}"
weight = 1
[backends."{{$backendName}}".LoadBalancer]
method = "drr"
[backends."{{$backendName}}".circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
method = "drr"
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
[frontends]
{{range $groupName, $groupServices := $groupedServiceMap}}
{{$service := index $groupServices 0}}
[frontends."{{$groupName}}"]
backend = "{{$groupName}}"
{{if hasLabel $service "frontend.priority"}}
priority = 100
{{end}}
{{range $key, $value := getLabelsWithPrefix $service "frontend.rule"}}
[frontends."{{$groupName}}".routes."{{$key}}"]
rule = "{{$value}}"
{{end}}
{{end}}
{{range $service := .Services}}
{{if isExposed $service}}
{{if eq $service.ServiceKind "Stateless"}}
[frontends."{{$service.Name}}"]
backend = "{{$service.Name}}"
{{if hasLabel $service "frontend.passHostHeader"}}
passHostHeader = {{getLabelValue $service "frontend.passHostHeader" ""}}
{{end}}
{{if hasLabel $service "frontend.whitelistSourceRange"}}
whitelistSourceRange = {{getLabelValue $service "frontend.whitelistSourceRange" ""}}
{{end}}
{{if hasLabel $service "frontend.priority"}}
priority = {{getLabelValue $service "frontend.priority" ""}}
{{end}}
{{if hasLabel $service "frontend.basicAuth"}}
basicAuth = {{getLabelValue $service "frontend.basicAuth" ""}}
{{end}}
{{if hasLabel $service "frontend.entryPoints"}}
entryPoints = {{getLabelValue $service "frontend.entryPoints" ""}}
{{end}}
{{range $key, $value := getLabelsWithPrefix $service "frontend.rule"}}
[frontends."{{$service.Name}}".routes."{{$key}}"]
rule = "{{$value}}"
{{end}}
{{else if eq $service.ServiceKind "Stateful"}}
{{range $partition := $service.Partitions}}
{{$partitionId := $partition.PartitionInformation.ID}}
{{if hasLabel $service "frontend.rule"}}
[frontends."{{$service.Name}}/{{$partitionId}}"]
backend = "{{getBackendName $service.Name $partition}}"
[frontends."{{$service.Name}}/{{$partitionId}}".routes.default]
rule = {{getLabelValue $service "frontend.rule.partition.$partitionId" ""}}
{{end}}
{{end}}
{{end}}
{{end}}
[frontends]
{{range $groupName, $groupServices := getGroupedServices .Services }}
{{ $service := index $groupServices 0 }}
[frontends."{{ $groupName }}"]
backend = "{{ $groupName }}"
priority = 50
{{range $key, $value := getFrontendRules $service }}
[frontends."{{ $groupName }}".routes."{{ $key }}"]
rule = "{{ $value }}"
{{end}}
{{end}}
{{range $service := .Services }}
{{if isEnabled $service }}
{{ $frontendName := $service.Name }}
{{if isStateless $service }}
[frontends."frontend-{{ $frontendName }}"]
backend = "{{ $service.Name }}"
passHostHeader = {{ getPassHostHeader $service }}
passTLSCert = {{ getPassTLSCert $service }}
priority = {{ getPriority $service }}
{{ $entryPoints := getEntryPoints $service }}
{{if $entryPoints }}
entryPoints = [{{range $entryPoints }}
"{{.}}",
{{end}}]
{{end}}
{{ $whitelistSourceRange := getWhitelistSourceRange $service }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
{{ $basicAuth := getBasicAuth $service }}
{{if $basicAuth }}
basicAuth = [{{range $basicAuth }}
"{{.}}",
{{end}}]
{{end}}
{{ $headers := getHeaders $service }}
{{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }}
SSLHost = "{{ $headers.SSLHost }}"
STSSeconds = {{ $headers.STSSeconds }}
STSIncludeSubdomains = {{ $headers.STSIncludeSubdomains }}
STSPreload = {{ $headers.STSPreload }}
ForceSTSHeader = {{ $headers.ForceSTSHeader }}
FrameDeny = {{ $headers.FrameDeny }}
CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}"
ContentTypeNosniff = {{ $headers.ContentTypeNosniff }}
BrowserXSSFilter = {{ $headers.BrowserXSSFilter }}
CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}"
PublicKey = "{{ $headers.PublicKey }}"
ReferrerPolicy = "{{ $headers.ReferrerPolicy }}"
IsDevelopment = {{ $headers.IsDevelopment }}
{{if $headers.AllowedHosts }}
AllowedHosts = [{{range $headers.AllowedHosts }}
"{{.}}",
{{end}}]
{{end}}
{{if $headers.HostsProxyHeaders }}
HostsProxyHeaders = [{{range $headers.HostsProxyHeaders }}
"{{.}}",
{{end}}]
{{end}}
{{if $headers.CustomRequestHeaders }}
[frontends."frontend-{{ $frontendName }}".headers.customRequestHeaders]
{{range $k, $v := $headers.CustomRequestHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $headers.CustomResponseHeaders }}
[frontends."frontend-{{ $frontendName }}".headers.customResponseHeaders]
{{range $k, $v := $headers.CustomResponseHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $headers.SSLProxyHeaders }}
[frontends."frontend-{{ $frontendName }}".headers.SSLProxyHeaders]
{{range $k, $v := $headers.SSLProxyHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
{{range $key, $value := getFrontendRules $service }}
[frontends."frontend-{{ $frontendName }}".routes."{{ $key }}"]
rule = "{{ $value }}"
{{end}}
{{else if isStateful $service}}
{{range $partition := $service.Partitions }}
{{ $partitionId := $partition.PartitionInformation.ID }}
{{if hasLabel $service "frontend.rule" }}
[frontends."{{ $service.Name }}/{{ $partitionId }}"]
backend = "{{ getBackendName $service.Name $partition }}"
[frontends."{{ $service.Name }}/{{ $partitionId }}".routes.default]
rule = {{ getLabelValue $service "frontend.rule.partition.$partitionId" "" }}
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
`

View file

@ -9,11 +9,9 @@ import (
// it belongs too and the replicas/partitions
type ServiceItemExtended struct {
sf.ServiceItem
HasHTTPEndpoint bool
IsHealthy bool
Application sf.ApplicationItem
Partitions []PartitionItemExtended
Labels map[string]string
Application sf.ApplicationItem
Partitions []PartitionItemExtended
Labels map[string]string
}
// PartitionItemExtended provides a flattened view
@ -32,8 +30,9 @@ type sfClient interface {
GetPartitions(appName, serviceName string) (*sf.PartitionItemsPage, error)
GetReplicas(appName, serviceName, partitionName string) (*sf.ReplicaItemsPage, error)
GetInstances(appName, serviceName, partitionName string) (*sf.InstanceItemsPage, error)
GetServiceExtension(appType, applicationVersion, serviceTypeName, extensionKey string, response interface{}) error
GetServiceExtensionMap(service *sf.ServiceItem, app *sf.ApplicationItem, extensionKey string) (map[string]string, error)
GetServiceLabels(service *sf.ServiceItem, app *sf.ApplicationItem, prefix string) (map[string]string, error)
GetProperties(name string) (bool, map[string]string, error)
}
// replicaInstance interface provides a unified interface

View file

@ -219,8 +219,30 @@ func (c Client) GetServiceExtension(appType, applicationVersion, serviceTypeName
return nil
}
// GetServiceExtensionMap returns all the extension xml specified
// in a Service's manifest file into (which must conform to ServiceExtensionLabels)
// a map[string]string
func (c Client) GetServiceExtensionMap(service *ServiceItem, app *ApplicationItem, extensionKey string) (map[string]string, error) {
extensionData := ServiceExtensionLabels{}
err := c.GetServiceExtension(app.TypeName, app.TypeVersion, service.TypeName, extensionKey, &extensionData)
if err != nil {
return nil, err
}
labels := map[string]string{}
if extensionData.Label != nil {
for _, label := range extensionData.Label {
labels[label.Key] = label.Value
}
}
return labels, nil
}
// GetProperties uses the Property Manager API to retrieve
// string properties from a name as a dictionary
// Property name is the path to the properties you would like to list.
// for example a serviceID
func (c Client) GetProperties(name string) (bool, map[string]string, error) {
nameExists, err := c.nameExists(name)
if err != nil {
@ -264,6 +286,8 @@ func (c Client) GetProperties(name string) (bool, map[string]string, error) {
// GetServiceLabels add labels from service manifest extensions and properties manager
// expects extension xml in <Label key="key">value</Label>
//
// Deprecated: Use GetProperties and GetServiceExtensionMap instead.
func (c Client) GetServiceLabels(service *ServiceItem, app *ApplicationItem, prefix string) (map[string]string, error) {
extensionData := ServiceExtensionLabels{}
err := c.GetServiceExtension(app.TypeName, app.TypeVersion, service.TypeName, prefix, &extensionData)