529 lines
17 KiB
Go
529 lines
17 KiB
Go
package mesos
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/BurntSushi/ty/fun"
|
|
"github.com/containous/traefik/log"
|
|
"github.com/containous/traefik/provider"
|
|
"github.com/containous/traefik/provider/label"
|
|
"github.com/containous/traefik/types"
|
|
"github.com/mesosphere/mesos-dns/records/state"
|
|
)
|
|
|
|
func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration {
|
|
var mesosFuncMap = template.FuncMap{
|
|
"getDomain": getFuncStringValue(label.TraefikDomain, p.Domain),
|
|
"getID": getID,
|
|
|
|
// Backend functions
|
|
"getBackendName": getBackendName,
|
|
"getCircuitBreaker": getCircuitBreaker,
|
|
"getLoadBalancer": getLoadBalancer,
|
|
"getMaxConn": getMaxConn,
|
|
"getHealthCheck": getHealthCheck,
|
|
"getServers": p.getServers,
|
|
"getHost": p.getHost,
|
|
"getServerPort": p.getServerPort,
|
|
|
|
// TODO Deprecated [breaking]
|
|
"getProtocol": getFuncApplicationStringValue(label.TraefikProtocol, label.DefaultProtocol),
|
|
// TODO Deprecated [breaking]
|
|
"getWeight": getFuncApplicationStringValue(label.TraefikWeight, label.DefaultWeight),
|
|
// TODO Deprecated [breaking] replaced by getBackendName
|
|
"getBackend": getBackend,
|
|
// TODO Deprecated [breaking]
|
|
"getPort": p.getPort,
|
|
|
|
// Frontend functions
|
|
"getFrontEndName": getFrontendName,
|
|
"getEntryPoints": getFuncSliceStringValue(label.TraefikFrontendEntryPoints),
|
|
"getBasicAuth": getFuncSliceStringValue(label.TraefikFrontendAuthBasic),
|
|
"getWhitelistSourceRange": getFuncSliceStringValue(label.TraefikFrontendWhitelistSourceRange),
|
|
"getPriority": getFuncStringValue(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
|
|
"getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
|
"getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
|
|
"getFrontendRule": p.getFrontendRule,
|
|
"getRedirect": getRedirect,
|
|
"getErrorPages": getErrorPages,
|
|
"getRateLimit": getRateLimit,
|
|
"getHeaders": getHeaders,
|
|
|
|
// TODO Deprecated [breaking]
|
|
"getFrontendBackend": getBackendName,
|
|
}
|
|
|
|
// filter tasks
|
|
filteredTasks := fun.Filter(func(task state.Task) bool {
|
|
return taskFilter(task, p.ExposedByDefault)
|
|
}, tasks).([]state.Task)
|
|
|
|
// Deprecated
|
|
var filteredApps []state.Task
|
|
uniqueApps := make(map[string]struct{})
|
|
for _, task := range filteredTasks {
|
|
if _, ok := uniqueApps[task.DiscoveryInfo.Name]; !ok {
|
|
uniqueApps[task.DiscoveryInfo.Name] = struct{}{}
|
|
filteredApps = append(filteredApps, task)
|
|
}
|
|
}
|
|
|
|
appsTasks := make(map[string][]state.Task)
|
|
for _, task := range filteredTasks {
|
|
if _, ok := appsTasks[task.DiscoveryInfo.Name]; !ok {
|
|
appsTasks[task.DiscoveryInfo.Name] = []state.Task{task}
|
|
} else {
|
|
appsTasks[task.DiscoveryInfo.Name] = append(appsTasks[task.DiscoveryInfo.Name], task)
|
|
}
|
|
}
|
|
|
|
templateObjects := struct {
|
|
ApplicationsTasks map[string][]state.Task
|
|
Applications []state.Task // Deprecated
|
|
Tasks []state.Task // Deprecated
|
|
Domain string
|
|
}{
|
|
ApplicationsTasks: appsTasks,
|
|
Applications: filteredApps, // Deprecated
|
|
Tasks: filteredTasks, // Deprecated
|
|
Domain: p.Domain,
|
|
}
|
|
|
|
configuration, err := p.GetConfiguration("templates/mesos.tmpl", mesosFuncMap, templateObjects)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
return configuration
|
|
}
|
|
|
|
func taskFilter(task state.Task, exposedByDefaultFlag bool) bool {
|
|
if len(task.DiscoveryInfo.Ports.DiscoveryPorts) == 0 {
|
|
log.Debugf("Filtering Mesos task without port %s", task.Name)
|
|
return false
|
|
}
|
|
|
|
if !isEnabled(task, exposedByDefaultFlag) {
|
|
log.Debugf("Filtering disabled Mesos task %s", task.DiscoveryInfo.Name)
|
|
return false
|
|
}
|
|
|
|
// filter indeterminable task port
|
|
portIndexLabel := getStringValue(task, label.TraefikPortIndex, "")
|
|
portValueLabel := getStringValue(task, label.TraefikPort, "")
|
|
if portIndexLabel != "" && portValueLabel != "" {
|
|
log.Debugf("Filtering Mesos task %s specifying both %q' and %q labels", task.Name, label.TraefikPortIndex, label.TraefikPort)
|
|
return false
|
|
}
|
|
if portIndexLabel != "" {
|
|
index, err := strconv.Atoi(portIndexLabel)
|
|
if err != nil || index < 0 || index > len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1 {
|
|
log.Debugf("Filtering Mesos task %s with unexpected value for %q label", task.Name, label.TraefikPortIndex)
|
|
return false
|
|
}
|
|
}
|
|
if portValueLabel != "" {
|
|
port, err := strconv.Atoi(portValueLabel)
|
|
if err != nil {
|
|
log.Debugf("Filtering Mesos task %s with unexpected value for %q label", task.Name, label.TraefikPort)
|
|
return false
|
|
}
|
|
|
|
var foundPort bool
|
|
for _, exposedPort := range task.DiscoveryInfo.Ports.DiscoveryPorts {
|
|
if port == exposedPort.Number {
|
|
foundPort = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !foundPort {
|
|
log.Debugf("Filtering Mesos task %s without a matching port for %q label", task.Name, label.TraefikPort)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// filter healthChecks
|
|
if task.Statuses != nil && len(task.Statuses) > 0 && task.Statuses[0].Healthy != nil && !*task.Statuses[0].Healthy {
|
|
log.Debugf("Filtering Mesos task %s with bad healthCheck", task.DiscoveryInfo.Name)
|
|
return false
|
|
|
|
}
|
|
return true
|
|
}
|
|
|
|
func getID(task state.Task) string {
|
|
return provider.Normalize(task.ID)
|
|
}
|
|
|
|
// Deprecated
|
|
func getBackend(task state.Task, apps []state.Task) string {
|
|
_, err := getApplication(task, apps)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return ""
|
|
}
|
|
return getBackendName(task)
|
|
}
|
|
|
|
func getBackendName(task state.Task) string {
|
|
if value := getStringValue(task, label.TraefikBackend, ""); len(value) > 0 {
|
|
return value
|
|
}
|
|
return provider.Normalize(task.DiscoveryInfo.Name)
|
|
}
|
|
|
|
func getFrontendName(task state.Task) string {
|
|
// TODO task.ID -> task.Name + task.ID
|
|
return provider.Normalize(task.ID)
|
|
}
|
|
|
|
func (p *Provider) getServerPort(task state.Task) string {
|
|
plv := getIntValue(task, label.TraefikPortIndex, math.MinInt32, len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1)
|
|
if plv >= 0 {
|
|
return strconv.Itoa(task.DiscoveryInfo.Ports.DiscoveryPorts[plv].Number)
|
|
}
|
|
|
|
if pv := getStringValue(task, label.TraefikPort, ""); len(pv) > 0 {
|
|
return pv
|
|
}
|
|
|
|
for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts {
|
|
return strconv.Itoa(port.Number)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Deprecated
|
|
func (p *Provider) getPort(task state.Task, applications []state.Task) string {
|
|
_, err := getApplication(task, applications)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return ""
|
|
}
|
|
|
|
plv := getIntValue(task, label.TraefikPortIndex, math.MinInt32, len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1)
|
|
if plv >= 0 {
|
|
return strconv.Itoa(task.DiscoveryInfo.Ports.DiscoveryPorts[plv].Number)
|
|
}
|
|
|
|
if pv := getStringValue(task, label.TraefikPort, ""); len(pv) > 0 {
|
|
return pv
|
|
}
|
|
|
|
for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts {
|
|
return strconv.Itoa(port.Number)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// getFrontendRule returns the frontend rule for the specified application, using
|
|
// it's label. It returns a default one (Host) if the label is not present.
|
|
func (p *Provider) getFrontendRule(task state.Task) string {
|
|
if v := getStringValue(task, label.TraefikFrontendRule, ""); len(v) > 0 {
|
|
return v
|
|
}
|
|
return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + p.Domain
|
|
}
|
|
|
|
func (p *Provider) getHost(task state.Task) string {
|
|
return task.IP(strings.Split(p.IPSources, ",")...)
|
|
}
|
|
|
|
func (p *Provider) getSubDomain(name string) string {
|
|
if p.GroupsAsSubDomains {
|
|
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
|
provider.ReverseStringSlice(&splitedName)
|
|
reverseName := strings.Join(splitedName, ".")
|
|
return reverseName
|
|
}
|
|
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
|
}
|
|
|
|
func getCircuitBreaker(task state.Task) *types.CircuitBreaker {
|
|
circuitBreaker := getStringValue(task, label.TraefikBackendCircuitBreakerExpression, "")
|
|
if len(circuitBreaker) == 0 {
|
|
return nil
|
|
}
|
|
return &types.CircuitBreaker{Expression: circuitBreaker}
|
|
}
|
|
|
|
func getLoadBalancer(task state.Task) *types.LoadBalancer {
|
|
if !hasPrefix(task, label.TraefikBackendLoadBalancer) {
|
|
return nil
|
|
}
|
|
|
|
method := getStringValue(task, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod)
|
|
|
|
lb := &types.LoadBalancer{
|
|
Method: method,
|
|
}
|
|
|
|
if getBoolValue(task, label.TraefikBackendLoadBalancerStickiness, false) {
|
|
cookieName := getStringValue(task, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName)
|
|
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
|
|
}
|
|
|
|
return lb
|
|
}
|
|
|
|
func getMaxConn(task state.Task) *types.MaxConn {
|
|
amount := getInt64Value(task, label.TraefikBackendMaxConnAmount, math.MinInt64)
|
|
extractorFunc := getStringValue(task, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
|
|
|
|
if amount == math.MinInt64 || len(extractorFunc) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return &types.MaxConn{
|
|
Amount: amount,
|
|
ExtractorFunc: extractorFunc,
|
|
}
|
|
}
|
|
|
|
func getHealthCheck(task state.Task) *types.HealthCheck {
|
|
path := getStringValue(task, label.TraefikBackendHealthCheckPath, "")
|
|
if len(path) == 0 {
|
|
return nil
|
|
}
|
|
|
|
port := getIntValue(task, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort, math.MaxInt32)
|
|
interval := getStringValue(task, label.TraefikBackendHealthCheckInterval, "")
|
|
|
|
return &types.HealthCheck{
|
|
Path: path,
|
|
Port: port,
|
|
Interval: interval,
|
|
}
|
|
}
|
|
|
|
func (p *Provider) getServers(tasks []state.Task) map[string]types.Server {
|
|
var servers map[string]types.Server
|
|
|
|
for _, task := range tasks {
|
|
if servers == nil {
|
|
servers = make(map[string]types.Server)
|
|
}
|
|
|
|
protocol := getStringValue(task, label.TraefikProtocol, label.DefaultProtocol)
|
|
host := p.getHost(task)
|
|
port := p.getServerPort(task)
|
|
|
|
serverName := "server-" + getID(task)
|
|
servers[serverName] = types.Server{
|
|
URL: fmt.Sprintf("%s://%s:%s", protocol, host, port),
|
|
Weight: getIntValue(task, label.TraefikWeight, label.DefaultWeightInt, math.MaxInt32),
|
|
}
|
|
}
|
|
|
|
return servers
|
|
}
|
|
|
|
func getRedirect(task state.Task) *types.Redirect {
|
|
if hasLabel(task, label.TraefikFrontendRedirectEntryPoint) {
|
|
return &types.Redirect{
|
|
EntryPoint: getStringValue(task, label.TraefikFrontendRedirectEntryPoint, ""),
|
|
}
|
|
}
|
|
|
|
if hasLabel(task, label.TraefikFrontendRedirectRegex) &&
|
|
hasLabel(task, label.TraefikFrontendRedirectReplacement) {
|
|
return &types.Redirect{
|
|
Regex: getStringValue(task, label.TraefikFrontendRedirectRegex, ""),
|
|
Replacement: getStringValue(task, label.TraefikFrontendRedirectReplacement, ""),
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getErrorPages(task state.Task) map[string]*types.ErrorPage {
|
|
prefix := label.Prefix + label.BaseFrontendErrorPage
|
|
labels := taskLabelsToMap(task)
|
|
return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage)
|
|
}
|
|
|
|
func getRateLimit(task state.Task) *types.RateLimit {
|
|
extractorFunc := getStringValue(task, label.TraefikFrontendRateLimitExtractorFunc, "")
|
|
if len(extractorFunc) == 0 {
|
|
return nil
|
|
}
|
|
|
|
labels := taskLabelsToMap(task)
|
|
prefix := label.Prefix + label.BaseFrontendRateLimit
|
|
limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit)
|
|
|
|
return &types.RateLimit{
|
|
ExtractorFunc: extractorFunc,
|
|
RateSet: limits,
|
|
}
|
|
}
|
|
|
|
func getHeaders(task state.Task) *types.Headers {
|
|
labels := taskLabelsToMap(task)
|
|
|
|
headers := &types.Headers{
|
|
CustomRequestHeaders: label.GetMapValue(labels, label.TraefikFrontendRequestHeaders),
|
|
CustomResponseHeaders: label.GetMapValue(labels, label.TraefikFrontendResponseHeaders),
|
|
SSLProxyHeaders: label.GetMapValue(labels, label.TraefikFrontendSSLProxyHeaders),
|
|
AllowedHosts: label.GetSliceStringValue(labels, label.TraefikFrontendAllowedHosts),
|
|
HostsProxyHeaders: label.GetSliceStringValue(labels, label.TraefikFrontendHostsProxyHeaders),
|
|
STSSeconds: label.GetInt64Value(labels, label.TraefikFrontendSTSSeconds, 0),
|
|
SSLRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLRedirect, false),
|
|
SSLTemporaryRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLTemporaryRedirect, false),
|
|
STSIncludeSubdomains: label.GetBoolValue(labels, label.TraefikFrontendSTSIncludeSubdomains, false),
|
|
STSPreload: label.GetBoolValue(labels, label.TraefikFrontendSTSPreload, false),
|
|
ForceSTSHeader: label.GetBoolValue(labels, label.TraefikFrontendForceSTSHeader, false),
|
|
FrameDeny: label.GetBoolValue(labels, label.TraefikFrontendFrameDeny, false),
|
|
ContentTypeNosniff: label.GetBoolValue(labels, label.TraefikFrontendContentTypeNosniff, false),
|
|
BrowserXSSFilter: label.GetBoolValue(labels, label.TraefikFrontendBrowserXSSFilter, false),
|
|
IsDevelopment: label.GetBoolValue(labels, label.TraefikFrontendIsDevelopment, false),
|
|
SSLHost: label.GetStringValue(labels, label.TraefikFrontendSSLHost, ""),
|
|
CustomFrameOptionsValue: label.GetStringValue(labels, label.TraefikFrontendCustomFrameOptionsValue, ""),
|
|
ContentSecurityPolicy: label.GetStringValue(labels, label.TraefikFrontendContentSecurityPolicy, ""),
|
|
PublicKey: label.GetStringValue(labels, label.TraefikFrontendPublicKey, ""),
|
|
ReferrerPolicy: label.GetStringValue(labels, label.TraefikFrontendReferrerPolicy, ""),
|
|
}
|
|
|
|
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
|
|
return nil
|
|
}
|
|
|
|
return headers
|
|
}
|
|
|
|
func isEnabled(task state.Task, exposedByDefault bool) bool {
|
|
return getBoolValue(task, label.TraefikEnable, exposedByDefault)
|
|
}
|
|
|
|
// Label functions
|
|
|
|
// Deprecated
|
|
func getFuncApplicationStringValue(labelName string, defaultValue string) func(task state.Task, applications []state.Task) string {
|
|
return func(task state.Task, applications []state.Task) string {
|
|
_, err := getApplication(task, applications)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return defaultValue
|
|
}
|
|
|
|
return getStringValue(task, labelName, defaultValue)
|
|
}
|
|
}
|
|
|
|
func getFuncStringValue(labelName string, defaultValue string) func(task state.Task) string {
|
|
return func(task state.Task) string {
|
|
return getStringValue(task, labelName, defaultValue)
|
|
}
|
|
}
|
|
|
|
func getFuncBoolValue(labelName string, defaultValue bool) func(task state.Task) bool {
|
|
return func(task state.Task) bool {
|
|
return getBoolValue(task, labelName, defaultValue)
|
|
}
|
|
}
|
|
|
|
func getFuncSliceStringValue(labelName string) func(task state.Task) []string {
|
|
return func(task state.Task) []string {
|
|
return getSliceStringValue(task, labelName)
|
|
}
|
|
}
|
|
|
|
func getStringValue(task state.Task, labelName string, defaultValue string) string {
|
|
for _, lbl := range task.Labels {
|
|
if lbl.Key == labelName && len(lbl.Value) > 0 {
|
|
return lbl.Value
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getBoolValue(task state.Task, labelName string, defaultValue bool) bool {
|
|
for _, lbl := range task.Labels {
|
|
if lbl.Key == labelName {
|
|
v, err := strconv.ParseBool(lbl.Value)
|
|
if err == nil {
|
|
return v
|
|
}
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getIntValue(task state.Task, labelName string, defaultValue int, maxValue int) int {
|
|
for _, lbl := range task.Labels {
|
|
if lbl.Key == labelName {
|
|
value, err := strconv.Atoi(lbl.Value)
|
|
if err == nil {
|
|
if value <= maxValue {
|
|
return value
|
|
}
|
|
log.Warnf("The value %q for %q exceed the max authorized value %q, falling back to %v.", lbl.Value, labelName, maxValue, defaultValue)
|
|
} else {
|
|
log.Warnf("Unable to parse %q: %q, falling back to %v. %v", labelName, lbl.Value, defaultValue, err)
|
|
}
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getSliceStringValue(task state.Task, labelName string) []string {
|
|
for _, lbl := range task.Labels {
|
|
if lbl.Key == labelName {
|
|
return label.SplitAndTrimString(lbl.Value, ",")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Deprecated
|
|
func getApplication(task state.Task, apps []state.Task) (state.Task, error) {
|
|
for _, app := range apps {
|
|
if app.DiscoveryInfo.Name == task.DiscoveryInfo.Name {
|
|
return app, nil
|
|
}
|
|
}
|
|
return state.Task{}, fmt.Errorf("unable to get Mesos application from task %s", task.DiscoveryInfo.Name)
|
|
}
|
|
|
|
func hasPrefix(task state.Task, prefix string) bool {
|
|
for _, lbl := range task.Labels {
|
|
if strings.HasPrefix(lbl.Key, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getInt64Value(task state.Task, labelName string, defaultValue int64) int64 {
|
|
for _, lbl := range task.Labels {
|
|
if lbl.Key == labelName {
|
|
value, err := strconv.ParseInt(lbl.Value, 10, 64)
|
|
if err != nil {
|
|
log.Warnf("Unable to parse %q: %q, falling back to %v. %v", labelName, lbl.Value, defaultValue, err)
|
|
}
|
|
return value
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func hasLabel(task state.Task, label string) bool {
|
|
for _, lbl := range task.Labels {
|
|
if lbl.Key == label {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func taskLabelsToMap(task state.Task) map[string]string {
|
|
labels := make(map[string]string)
|
|
for _, lbl := range task.Labels {
|
|
labels[lbl.Key] = lbl.Value
|
|
}
|
|
return labels
|
|
}
|