refactor(ecs): rewrite configuration system.

This commit is contained in:
Fernandez Ludovic 2017-12-02 19:30:16 +01:00 committed by Traefiker
parent be718aea11
commit c705d6f9b3
5 changed files with 642 additions and 857 deletions

View file

@ -305,7 +305,7 @@ func templatesDockerTmpl() (*asset, error) {
var _templatesEcsTmpl = []byte(`[backends]{{range $serviceName, $instances := .Services}}
[backends.backend-{{ $serviceName }}.loadbalancer]
method = "{{ getLoadBalancerMethod $instances}}"
sticky = {{ getLoadBalancerSticky $instances}}
sticky = {{ getSticky $instances}}
{{if hasStickinessLabel $instances}}
[backends.backend-{{ $serviceName }}.loadbalancer.stickiness]
cookieName = "{{getStickinessCookieName $instances}}"

170
provider/ecs/config.go Normal file
View file

@ -0,0 +1,170 @@
package ecs
import (
"strconv"
"strings"
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
)
// buildConfiguration fills the config template with the given instances
func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types.Configuration, error) {
var ecsFuncMap = template.FuncMap{
"filterFrontends": filterFrontends,
"getFrontendRule": p.getFrontendRule,
"getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic),
"getLoadBalancerMethod": getFuncFirstStringValue(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
"getSticky": getSticky,
"hasStickinessLabel": getFuncFirstBoolValue(label.TraefikBackendLoadBalancerStickiness, false),
"getStickinessCookieName": getFuncFirstStringValue(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
"getProtocol": getFuncStringValue(label.TraefikProtocol, label.DefaultProtocol),
"getHost": getHost,
"getPort": getPort,
"getWeight": getFuncStringValue(label.TraefikWeight, label.DefaultWeight),
"getPassHostHeader": getFuncStringValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
"getPriority": getFuncStringValue(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
"getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints),
"hasHealthCheckLabels": hasFuncFirst(label.TraefikBackendHealthCheckPath),
"getHealthCheckPath": getFuncFirstStringValue(label.TraefikBackendHealthCheckPath, ""),
"getHealthCheckInterval": getFuncFirstStringValue(label.TraefikBackendHealthCheckInterval, ""),
}
return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct {
Services map[string][]ecsInstance
}{
services,
})
}
func (p *Provider) getFrontendRule(i ecsInstance) string {
defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + p.Domain
return getStringValue(i, label.TraefikFrontendRule, defaultRule)
}
// TODO: Deprecated
// Deprecated replaced by Stickiness
func getSticky(instances []ecsInstance) string {
if hasFirst(instances, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
}
return getFirstStringValue(instances, label.TraefikBackendLoadBalancerSticky, "false")
}
func getHost(i ecsInstance) string {
return *i.machine.PrivateIpAddress
}
func getPort(i ecsInstance) string {
if value := getStringValue(i, label.TraefikPort, ""); len(value) > 0 {
return value
}
return strconv.FormatInt(*i.container.NetworkBindings[0].HostPort, 10)
}
func filterFrontends(instances []ecsInstance) []ecsInstance {
byName := make(map[string]struct{})
return fun.Filter(func(i ecsInstance) bool {
_, found := byName[i.Name]
if !found {
byName[i.Name] = struct{}{}
}
return !found
}, instances).([]ecsInstance)
}
// Label functions
func getFuncStringValue(labelName string, defaultValue string) func(i ecsInstance) string {
return func(i ecsInstance) string {
return getStringValue(i, labelName, defaultValue)
}
}
func getFuncSliceString(labelName string) func(i ecsInstance) []string {
return func(i ecsInstance) []string {
return getSliceString(i, labelName)
}
}
func hasFuncFirst(labelName string) func(instances []ecsInstance) bool {
return func(instances []ecsInstance) bool {
return hasFirst(instances, labelName)
}
}
func getFuncFirstStringValue(labelName string, defaultValue string) func(instances []ecsInstance) string {
return func(instances []ecsInstance) string {
return getFirstStringValue(instances, labelName, defaultValue)
}
}
func getFuncFirstBoolValue(labelName string, defaultValue bool) func(instances []ecsInstance) bool {
return func(instances []ecsInstance) bool {
if len(instances) < 0 {
return defaultValue
}
return getBoolValue(instances[0], labelName, defaultValue)
}
}
func getStringValue(i ecsInstance, labelName string, defaultValue string) string {
if v, ok := i.containerDefinition.DockerLabels[labelName]; ok {
if v == nil {
return defaultValue
}
if len(*v) == 0 {
return defaultValue
}
return *v
}
return defaultValue
}
func getBoolValue(i ecsInstance, labelName string, defaultValue bool) bool {
rawValue, ok := i.containerDefinition.DockerLabels[labelName]
if ok {
if rawValue != nil {
v, err := strconv.ParseBool(*rawValue)
if err == nil {
return v
}
}
}
return defaultValue
}
func getSliceString(i ecsInstance, labelName string) []string {
if value, ok := i.containerDefinition.DockerLabels[labelName]; ok {
if value == nil {
return nil
}
if len(*value) == 0 {
return nil
}
return label.SplitAndTrimString(*value, ",")
}
return nil
}
func hasFirst(instances []ecsInstance, labelName string) bool {
if len(instances) > 0 {
v, ok := instances[0].containerDefinition.DockerLabels[labelName]
return ok && v != nil && len(*v) != 0
}
return false
}
func getFirstStringValue(instances []ecsInstance, labelName string, defaultValue string) string {
if len(instances) == 0 {
return defaultValue
}
return getStringValue(instances[0], labelName, defaultValue)
}
func isEnabled(i ecsInstance, exposedByDefault bool) bool {
return getBoolValue(i, label.TraefikEnable, exposedByDefault)
}

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,7 @@ package ecs
import (
"context"
"fmt"
"strconv"
"strings"
"text/template"
"time"
"github.com/BurntSushi/ty/fun"
@ -21,6 +19,7 @@ import (
"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"
)
@ -178,34 +177,6 @@ func wrapAws(ctx context.Context, req *request.Request) error {
return req.Send()
}
// generateECSConfig fills the config template with the given instances
func (p *Provider) generateECSConfig(services map[string][]ecsInstance) (*types.Configuration, error) {
var ecsFuncMap = template.FuncMap{
"filterFrontends": p.filterFrontends,
"getFrontendRule": p.getFrontendRule,
"getBasicAuth": p.getBasicAuth,
"getLoadBalancerMethod": p.getLoadBalancerMethod,
"getLoadBalancerSticky": p.getLoadBalancerSticky,
"hasStickinessLabel": p.hasStickinessLabel,
"getStickinessCookieName": p.getStickinessCookieName,
"getProtocol": p.getProtocol,
"getHost": p.getHost,
"getPort": p.getPort,
"getWeight": p.getWeight,
"getPassHostHeader": p.getPassHostHeader,
"getPriority": p.getPriority,
"getEntryPoints": p.getEntryPoints,
"hasHealthCheckLabels": p.hasHealthCheckLabels,
"getHealthCheckPath": p.getHealthCheckPath,
"getHealthCheckInterval": p.getHealthCheckInterval,
}
return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct {
Services map[string][]ecsInstance
}{
services,
})
}
func (p *Provider) loadECSConfig(ctx context.Context, client *awsClient) (*types.Configuration, error) {
instances, err := p.listInstances(ctx, client)
if err != nil {
@ -223,7 +194,7 @@ func (p *Provider) loadECSConfig(ctx context.Context, client *awsClient) (*types
services[instance.Name] = []ecsInstance{instance}
}
}
return p.generateECSConfig(services)
return p.buildConfiguration(services)
}
// Find all running Provider tasks in a cluster, also collect the task definitions (for docker labels)
@ -285,7 +256,7 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
continue
}
chunkedTaskArns := p.chunkedTaskArns(taskArns)
chunkedTaskArns := chunkedTaskArns(taskArns)
var tasks []*ecs.Task
for _, arns := range chunkedTaskArns {
@ -424,22 +395,14 @@ func (p *Provider) lookupTaskDefinitions(ctx context.Context, client *awsClient,
return taskDefinitions, nil
}
func (p *Provider) label(i ecsInstance, k string) string {
if v, found := i.containerDefinition.DockerLabels[k]; found {
return *v
}
return ""
}
func (p *Provider) filterInstance(i ecsInstance) bool {
if labelPort := p.label(i, types.LabelPort); len(i.container.NetworkBindings) == 0 && labelPort == "" {
if labelPort := getStringValue(i, label.TraefikPort, ""); len(i.container.NetworkBindings) == 0 && labelPort == "" {
log.Debugf("Filtering ecs instance without port %s (%s)", i.Name, i.ID)
return false
}
if i.machine == nil ||
i.machine.State == nil ||
i.machine.State.Name == nil {
if i.machine == nil || i.machine.State == nil || i.machine.State.Name == nil {
log.Debugf("Filtering ecs instance in an missing ec2 information %s (%s)", i.Name, i.ID)
return false
}
@ -454,99 +417,20 @@ func (p *Provider) filterInstance(i ecsInstance) bool {
return false
}
label := p.label(i, types.LabelEnable)
enabled := p.ExposedByDefault && label != "false" || label == "true"
if !enabled {
log.Debugf("Filtering disabled ecs instance %s (%s) (traefik.enabled = '%s')", i.Name, i.ID, label)
if !isEnabled(i, p.ExposedByDefault) {
log.Debugf("Filtering disabled ecs instance %s (%s)", i.Name, i.ID)
return false
}
return true
}
func (p *Provider) filterFrontends(instances []ecsInstance) []ecsInstance {
byName := make(map[string]bool)
return fun.Filter(func(i ecsInstance) bool {
if _, found := byName[i.Name]; !found {
byName[i.Name] = true
return true
}
return false
}, instances).([]ecsInstance)
}
func (p *Provider) getFrontendRule(i ecsInstance) string {
if label := p.label(i, types.LabelFrontendRule); label != "" {
return label
}
return "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + p.Domain
}
func (p *Provider) getBasicAuth(i ecsInstance) []string {
label := p.label(i, types.LabelFrontendAuthBasic)
if label != "" {
return strings.Split(label, ",")
}
return []string{}
}
func (p *Provider) getFirstInstanceLabel(instances []ecsInstance, labelName string) string {
if len(instances) > 0 {
return p.label(instances[0], labelName)
}
return ""
}
func (p *Provider) getLoadBalancerSticky(instances []ecsInstance) string {
if len(instances) > 0 {
label := p.getFirstInstanceLabel(instances, types.LabelBackendLoadbalancerSticky)
if label != "" {
log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness)
return label
}
}
return "false"
}
func (p *Provider) hasStickinessLabel(instances []ecsInstance) bool {
stickinessLabel := p.getFirstInstanceLabel(instances, types.LabelBackendLoadbalancerStickiness)
return len(stickinessLabel) > 0 && strings.EqualFold(strings.TrimSpace(stickinessLabel), "true")
}
func (p *Provider) getStickinessCookieName(instances []ecsInstance) string {
return p.getFirstInstanceLabel(instances, types.LabelBackendLoadbalancerStickinessCookieName)
}
func (p *Provider) getLoadBalancerMethod(instances []ecsInstance) string {
if len(instances) > 0 {
label := p.label(instances[0], types.LabelBackendLoadbalancerMethod)
if label != "" {
return label
}
}
return "wrr"
}
func (p *Provider) hasHealthCheckLabels(instances []ecsInstance) bool {
return p.getHealthCheckPath(instances) != ""
}
func (p *Provider) getHealthCheckPath(instances []ecsInstance) string {
return p.getFirstInstanceLabel(instances, types.LabelBackendHealthcheckPath)
}
func (p *Provider) getHealthCheckInterval(instances []ecsInstance) string {
return p.getFirstInstanceLabel(instances, types.LabelBackendHealthcheckInterval)
}
// Provider expects no more than 100 parameters be passed to a DescribeTask call; thus, pack
// each string into an array capped at 100 elements
func (p *Provider) chunkedTaskArns(tasks []*string) [][]*string {
func chunkedTaskArns(tasks []*string) [][]*string {
var chunkedTasks [][]*string
for i := 0; i < len(tasks); i += 100 {
sliceEnd := -1
var sliceEnd int
if i+100 < len(tasks) {
sliceEnd = i + 100
} else {
@ -556,49 +440,3 @@ func (p *Provider) chunkedTaskArns(tasks []*string) [][]*string {
}
return chunkedTasks
}
func (p *Provider) getProtocol(i ecsInstance) string {
if label := p.label(i, types.LabelProtocol); label != "" {
return label
}
return "http"
}
func (p *Provider) getHost(i ecsInstance) string {
return *i.machine.PrivateIpAddress
}
func (p *Provider) getPort(i ecsInstance) string {
if port := p.label(i, types.LabelPort); port != "" {
return port
}
return strconv.FormatInt(*i.container.NetworkBindings[0].HostPort, 10)
}
func (p *Provider) getWeight(i ecsInstance) string {
if label := p.label(i, types.LabelWeight); label != "" {
return label
}
return "0"
}
func (p *Provider) getPassHostHeader(i ecsInstance) string {
if label := p.label(i, types.LabelFrontendPassHostHeader); label != "" {
return label
}
return "true"
}
func (p *Provider) getPriority(i ecsInstance) string {
if label := p.label(i, types.LabelFrontendPriority); label != "" {
return label
}
return "0"
}
func (p *Provider) getEntryPoints(i ecsInstance) []string {
if label := p.label(i, types.LabelFrontendEntryPoints); label != "" {
return strings.Split(label, ",")
}
return []string{}
}

View file

@ -1,7 +1,7 @@
[backends]{{range $serviceName, $instances := .Services}}
[backends.backend-{{ $serviceName }}.loadbalancer]
method = "{{ getLoadBalancerMethod $instances}}"
sticky = {{ getLoadBalancerSticky $instances}}
sticky = {{ getSticky $instances}}
{{if hasStickinessLabel $instances}}
[backends.backend-{{ $serviceName }}.loadbalancer.stickiness]
cookieName = "{{getStickinessCookieName $instances}}"