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

9
Gopkg.lock generated
View file

@ -247,8 +247,8 @@
[[projects]] [[projects]]
name = "github.com/containous/traefik-extra-service-fabric" name = "github.com/containous/traefik-extra-service-fabric"
packages = ["."] packages = ["."]
revision = "ca1fb57108293caad285b1c366b763f6c6ab71c9" revision = "a0b20089e99069884b060875fc015c13a23e7953"
version = "v1.0.5" version = "v1.1.0"
[[projects]] [[projects]]
name = "github.com/coreos/bbolt" name = "github.com/coreos/bbolt"
@ -745,10 +745,9 @@
version = "v1.3.7" version = "v1.3.7"
[[projects]] [[projects]]
branch = "master"
name = "github.com/jjcollinge/servicefabric" name = "github.com/jjcollinge/servicefabric"
packages = ["."] packages = ["."]
revision = "8026935326c842b71dee8e2329c1fda41a7a92f4" revision = "8eebe170fa1ba25d3dfb928b3f86a7313b13b9fe"
[[projects]] [[projects]]
name = "github.com/jmespath/go-jmespath" name = "github.com/jmespath/go-jmespath"
@ -1633,6 +1632,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "39b400d9928cce714a46cae410074fd2622a097a9e0faba76a7b21537fabd728" inputs-digest = "ab328aeda9dbd2c4dc87061c25dbfbd151dbdc5946b6f512f676b39bebba8d8e"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View file

@ -66,7 +66,7 @@
[[constraint]] [[constraint]]
name = "github.com/containous/traefik-extra-service-fabric" name = "github.com/containous/traefik-extra-service-fabric"
version = "1.0.5" version = "1.1.0"
[[constraint]] [[constraint]]
name = "github.com/coreos/go-systemd" name = "github.com/coreos/go-systemd"

View file

@ -69,10 +69,10 @@ Here is an example of an extension setting Træfik labels:
</StatelessServiceType> </StatelessServiceType>
``` ```
#### Property Manager #### Property Manager
Set Labels with the property manager API to overwrite and add labels, while your service is running. Set Labels with the property manager API to overwrite and add labels, while your service is running.
Here is an example of adding a frontend rule using the property manager API. Here is an example of adding a frontend rule using the property manager API.
```shell ```shell
curl -X PUT \ curl -X PUT \
@ -92,23 +92,63 @@ curl -X PUT \
## Available Labels ## Available Labels
Labels, set through extensions or the property manager, can be used on services to override default behaviour. Labels, set through extensions or the property manager, can be used on services to override default behavior.
| Label | Description | | Label | Description |
|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. | | `traefik.enable=false` | Disable this container in Træfik |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. | | `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm | | `traefik.backend.group.name` | Group all services with the same name into a single backend in Træfik |
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | | `traefik.backend.group.weight` | Set the weighting of the current services nodes in the backend group |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | | `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend | | `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
| `traefik.backend.weight=10` | Assign this weight to the container | | `traefik.backend.healthcheck.interval=1s` | Define the health check interval. |
| `traefik.expose=true` | Expose this service using træfik | | `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Defaults to SF address. | | `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. | | `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
| `traefik.frontend.priority=10` | Override default frontend priority | | `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints` | | `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `traefik.frontend.auth.basic=EXPR` | Set basic authentication for that frontend in CSV format: `User:Hash,User:Hash` | | `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. | | `traefik.backend.weight=10` | Assign this weight to the container |
| `traefik.backend.group.name` | Group all services with the same name into a single backend in Træfik | | `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.backend.group.weight` | Set the weighting of the current services nodes in the backend group | | `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
| `traefik.frontend.passTLSCert=true` | Forward TLS Client certificates to the backend. |
| `traefik.frontend.priority=10` | Override default frontend priority |
| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) |
| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.replacement`. |
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.<br>Must be set with `traefik.frontend.redirect.regex`. |
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Defaults to SF address. |
| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
### Custom Headers
| Label | Description |
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
### Security Headers
| Label | Description |
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
| `traefik.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |

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 package servicefabric
import ( import (
"encoding/json"
"errors"
"net/http" "net/http"
"strings"
"text/template"
"time" "time"
"github.com/cenk/backoff" "github.com/cenk/backoff"
"github.com/containous/traefik/job" "github.com/containous/traefik/job"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/provider" "github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
sf "github.com/jjcollinge/servicefabric" sf "github.com/jjcollinge/servicefabric"
@ -19,7 +16,7 @@ import (
var _ provider.Provider = (*Provider)(nil) var _ provider.Provider = (*Provider)(nil)
const traefikLabelPrefix = "traefik" const traefikServiceFabricExtensionKey = "Traefik"
// Provider holds for configuration for the provider // Provider holds for configuration for the provider
type Provider struct { type Provider struct {
@ -93,104 +90,6 @@ func (p *Provider) updateConfig(configurationChan chan<- types.ConfigMessage, po
return nil 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) { func getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) {
apps, err := sfClient.GetApplications() apps, err := sfClient.GetApplications()
if err != nil { if err != nil {
@ -210,7 +109,7 @@ func getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) {
Application: app, Application: app,
} }
if labels, err := sfClient.GetServiceLabels(&service, &app, traefikLabelPrefix); err != nil { if labels, err := getLabels(sfClient, &service, &app); err != nil {
log.Error(err) log.Error(err)
} else { } else {
item.Labels = labels item.Labels = labels
@ -222,9 +121,9 @@ func getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) {
for _, partition := range partitions.Items { for _, partition := range partitions.Items {
partitionExt := PartitionItemExtended{PartitionItem: partition} partitionExt := PartitionItemExtended{PartitionItem: partition}
if partition.ServiceKind == "Stateful" { if isStateful(item) {
partitionExt.Replicas = getValidReplicas(sfClient, app, service, partition) partitionExt.Replicas = getValidReplicas(sfClient, app, service, partition)
} else if partition.ServiceKind == "Stateless" { } else if isStateless(item) {
partitionExt.Instances = getValidInstances(sfClient, app, service, partition) partitionExt.Instances = getValidInstances(sfClient, app, service, partition)
} else { } else {
log.Errorf("Unsupported service kind %s in service %s", partition.ServiceKind, service.Name) 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 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 { func isPrimary(instance replicaInstance) bool {
_, data := instance.GetReplicaData() _, data := instance.GetReplicaData()
return data.ReplicaRole == "Primary" return data.ReplicaRole == "Primary"
@ -311,26 +185,21 @@ func hasHTTPEndpoint(instanceData *sf.ReplicaItemBase) bool {
return err == nil return err == nil
} }
func decodeEndpointData(endpointData string) (map[string]string, error) { // Return a set of labels from the Extension and Property manager
var endpointsMap map[string]map[string]string // 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) {
if endpointData == "" { labels, err := sfClient.GetServiceExtensionMap(service, app, traefikServiceFabricExtensionKey)
return nil, errors.New("endpoint data is empty")
}
err := json.Unmarshal([]byte(endpointData), &endpointsMap)
if err != nil { if err != nil {
log.Errorf("Error retrieving serviceExtensionMap: %v", err)
return nil, err return nil, err
} }
endpoints, endpointsExist := endpointsMap["Endpoints"] if label.GetBoolValue(labels, traefikSFEnableLabelOverrides, traefikSFEnableLabelOverridesDefault) {
if !endpointsExist { if exists, properties, err := sfClient.GetProperties(service.ID); err == nil && exists {
return nil, errors.New("endpoint doesn't exist in endpoint data") for key, value := range properties {
labels[key] = value
}
}
} }
return labels, nil
return endpoints, nil
}
func getBackendName(service ServiceItemExtended, partition PartitionItemExtended) string {
return provider.Normalize(service.Name + partition.PartitionInformation.ID)
} }

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 package servicefabric
const tmpl = ` const tmpl = `
{{$groupedServiceMap := getServices .Services "backend.group.name"}}
[backends] [backends]
{{range $aggName, $aggServices := $groupedServiceMap }} {{range $aggName, $aggServices := getGroupedServices .Services }}
[backends."{{$aggName}}"] [backends."{{ $aggName }}"]
{{range $service := $aggServices}} {{range $service := $aggServices }}
{{range $partition := $service.Partitions}} {{range $partition := $service.Partitions }}
{{range $instance := $partition.Instances}} {{range $instance := $partition.Instances }}
[backends."{{$aggName}}".servers."{{$service.ID}}-{{$instance.ID}}"] [backends."{{ $aggName }}".servers."{{ $service.ID }}-{{ $instance.ID }}"]
url = "{{getDefaultEndpoint $instance}}" url = "{{ getDefaultEndpoint $instance }}"
weight = {{getLabelValue $service "backend.group.weight" "1"}} weight = {{ getGroupedWeight $service }}
{{end}} {{end}}
{{end}} {{end}}
{{end}} {{end}}
{{end}} {{end}}
{{range $service := .Services}}
{{range $partition := $service.Partitions}} {{range $service := .Services }}
{{if eq $partition.ServiceKind "Stateless"}} {{if isEnabled $service }}
[backends."{{$service.Name}}"] {{range $partition := $service.Partitions }}
[backends."{{$service.Name}}".LoadBalancer]
{{if hasLabel $service "backend.loadbalancer.method"}} {{if isStateless $service }}
method = "{{getLabelValue $service "backend.loadbalancer.method" "" }}"
{{else}} {{ $backendName := $service.Name }}
method = "drr" [backends."{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $service }}
{{if $circuitBreaker }}
[backends."{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}} {{end}}
{{if hasLabel $service "backend.healthcheck"}} {{ $loadBalancer := getLoadBalancer $service }}
[backends."{{$service.Name}}".healthcheck] {{if $loadBalancer }}
path = "{{getLabelValue $service "backend.healthcheck" ""}}" [backends."{{ $backendName }}".loadBalancer]
interval = "{{getLabelValue $service "backend.healthcheck.interval" "10s"}}" method = "{{ $loadBalancer.Method }}"
sticky = {{ $loadBalancer.Sticky }}
{{if $loadBalancer.Stickiness }}
[backends."{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ $loadBalancer.Stickiness.CookieName }}"
{{end}}
{{end}} {{end}}
{{if hasLabel $service "backend.loadbalancer.stickiness"}} {{ $maxConn := getMaxConn $service }}
[backends."{{$service.Name}}".LoadBalancer.stickiness] {{if $maxConn }}
[backends."{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}} {{end}}
{{if hasLabel $service "backend.circuitbreaker"}} {{ $healthCheck := getHealthCheck $service }}
[backends."{{$service.Name}}".circuitbreaker] {{if $healthCheck }}
expression = "{{getLabelValue $service "backend.circuitbreaker" ""}}" [backends."{{ $backendName }}".healthCheck]
{{end}} path = "{{ $healthCheck.Path }}"
port = {{ $healthCheck.Port }}
{{if hasLabel $service "backend.maxconn.amount"}} interval = "{{ $healthCheck.Interval }}"
[backends."{{$service.Name}}".maxconn]
amount = {{getLabelValue $service "backend.maxconn.amount" ""}}
{{if hasLabel $service "backend.maxconn.extractorfunc"}}
extractorfunc = "{{getLabelValue $service "backend.maxconn.extractorfunc" ""}}"
{{end}}
{{end}} {{end}}
{{range $instance := $partition.Instances}} {{range $instance := $partition.Instances}}
[backends."{{$service.Name}}".servers."{{$instance.ID}}"] [backends."{{ $service.Name }}".servers."{{ $instance.ID }}"]
url = "{{getDefaultEndpoint $instance}}" url = "{{ getDefaultEndpoint $instance }}"
weight = {{getLabelValue $service "backend.weight" "1"}} weight = {{ getLabelValue $service "backend.weight" "1" }}
{{end}} {{end}}
{{else if eq $partition.ServiceKind "Stateful"}}
{{else if isStateful $service}}
{{range $replica := $partition.Replicas}} {{range $replica := $partition.Replicas}}
{{if isPrimary $replica}} {{if isPrimary $replica}}
{{ $backendName := getBackendName $service $partition }}
{{$backendName := getBackendName $service.Name $partition}} [backends."{{ $backendName }}".servers."{{ $replica.ID }}"]
[backends."{{$backendName}}".servers."{{$replica.ID}}"] url = "{{ getDefaultEndpoint $replica }}"
url = "{{getDefaultEndpoint $replica}}" weight = 1
weight = 1
[backends."{{$backendName}}".LoadBalancer] [backends."{{$backendName}}".LoadBalancer]
method = "drr" method = "drr"
[backends."{{$backendName}}".circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
{{end}} {{end}}
{{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}}
{{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}} {{end}}
` `

View file

@ -9,11 +9,9 @@ import (
// it belongs too and the replicas/partitions // it belongs too and the replicas/partitions
type ServiceItemExtended struct { type ServiceItemExtended struct {
sf.ServiceItem sf.ServiceItem
HasHTTPEndpoint bool Application sf.ApplicationItem
IsHealthy bool Partitions []PartitionItemExtended
Application sf.ApplicationItem Labels map[string]string
Partitions []PartitionItemExtended
Labels map[string]string
} }
// PartitionItemExtended provides a flattened view // PartitionItemExtended provides a flattened view
@ -32,8 +30,9 @@ type sfClient interface {
GetPartitions(appName, serviceName string) (*sf.PartitionItemsPage, error) GetPartitions(appName, serviceName string) (*sf.PartitionItemsPage, error)
GetReplicas(appName, serviceName, partitionName string) (*sf.ReplicaItemsPage, error) GetReplicas(appName, serviceName, partitionName string) (*sf.ReplicaItemsPage, error)
GetInstances(appName, serviceName, partitionName string) (*sf.InstanceItemsPage, 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) 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 // replicaInstance interface provides a unified interface

View file

@ -219,8 +219,30 @@ func (c Client) GetServiceExtension(appType, applicationVersion, serviceTypeName
return nil 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 // GetProperties uses the Property Manager API to retrieve
// string properties from a name as a dictionary // 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) { func (c Client) GetProperties(name string) (bool, map[string]string, error) {
nameExists, err := c.nameExists(name) nameExists, err := c.nameExists(name)
if err != nil { 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 // GetServiceLabels add labels from service manifest extensions and properties manager
// expects extension xml in <Label key="key">value</Label> // 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) { func (c Client) GetServiceLabels(service *ServiceItem, app *ApplicationItem, prefix string) (map[string]string, error) {
extensionData := ServiceExtensionLabels{} extensionData := ServiceExtensionLabels{}
err := c.GetServiceExtension(app.TypeName, app.TypeVersion, service.TypeName, prefix, &extensionData) err := c.GetServiceExtension(app.TypeName, app.TypeVersion, service.TypeName, prefix, &extensionData)