623 lines
18 KiB
Go
623 lines
18 KiB
Go
package kv
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/BurntSushi/ty/fun"
|
|
"github.com/abronan/valkeyrie/store"
|
|
"github.com/containous/flaeg"
|
|
"github.com/containous/traefik/log"
|
|
"github.com/containous/traefik/provider/label"
|
|
"github.com/containous/traefik/tls"
|
|
"github.com/containous/traefik/types"
|
|
)
|
|
|
|
func (p *Provider) buildConfiguration() *types.Configuration {
|
|
templateObjects := struct {
|
|
Prefix string
|
|
}{
|
|
// Allow `/traefik/alias` to supersede `p.Prefix`
|
|
Prefix: strings.TrimSuffix(p.get(p.Prefix, p.Prefix+pathAlias), pathSeparator),
|
|
}
|
|
|
|
var KvFuncMap = template.FuncMap{
|
|
"List": p.list,
|
|
"ListServers": p.listServers,
|
|
"Get": p.get,
|
|
"GetBool": p.getBool,
|
|
"GetInt": p.getInt,
|
|
"GetInt64": p.getInt64,
|
|
"GetList": p.getList,
|
|
"SplitGet": p.splitGet,
|
|
"Last": p.last,
|
|
"Has": p.has,
|
|
|
|
"getTLSSection": p.getTLSSection,
|
|
|
|
// Frontend functions
|
|
"getBackendName": p.getFuncString(pathFrontendBackend, ""),
|
|
"getPriority": p.getFuncInt(pathFrontendPriority, label.DefaultFrontendPriority),
|
|
"getPassHostHeader": p.getPassHostHeader(),
|
|
"getPassTLSCert": p.getFuncBool(pathFrontendPassTLSCert, label.DefaultPassTLSCert),
|
|
"getEntryPoints": p.getFuncList(pathFrontendEntryPoints),
|
|
"getBasicAuth": p.getFuncList(pathFrontendBasicAuth),
|
|
"getRoutes": p.getRoutes,
|
|
"getRedirect": p.getRedirect,
|
|
"getErrorPages": p.getErrorPages,
|
|
"getRateLimit": p.getRateLimit,
|
|
"getHeaders": p.getHeaders,
|
|
"getWhiteList": p.getWhiteList,
|
|
|
|
// Backend functions
|
|
"getServers": p.getServers,
|
|
"getCircuitBreaker": p.getCircuitBreaker,
|
|
"getLoadBalancer": p.getLoadBalancer,
|
|
"getMaxConn": p.getMaxConn,
|
|
"getHealthCheck": p.getHealthCheck,
|
|
"getBuffering": p.getBuffering,
|
|
"getSticky": p.getSticky, // Deprecated [breaking]
|
|
"hasStickinessLabel": p.hasStickinessLabel, // Deprecated [breaking]
|
|
"getStickinessCookieName": p.getStickinessCookieName, // Deprecated [breaking]
|
|
}
|
|
|
|
configuration, err := p.GetConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
|
|
for key, frontend := range configuration.Frontends {
|
|
if _, ok := configuration.Backends[frontend.Backend]; !ok {
|
|
delete(configuration.Frontends, key)
|
|
}
|
|
}
|
|
|
|
return configuration
|
|
}
|
|
|
|
// Deprecated
|
|
func (p *Provider) getPassHostHeader() func(rootPath string) bool {
|
|
return func(rootPath string) bool {
|
|
rawValue := p.get("", rootPath, pathFrontendPassHostHeader)
|
|
|
|
if len(rawValue) > 0 {
|
|
value, err := strconv.ParseBool(rawValue)
|
|
if err != nil {
|
|
log.Errorf("Invalid value for %s %s: %s", rootPath, pathFrontendPassHostHeader, rawValue)
|
|
return label.DefaultPassHostHeader
|
|
}
|
|
return value
|
|
}
|
|
|
|
return p.getBool(label.DefaultPassHostHeader, rootPath, pathFrontendPassHostHeaderDeprecated)
|
|
}
|
|
}
|
|
|
|
// Deprecated
|
|
func (p *Provider) getSticky(rootPath string) bool {
|
|
stickyValue := p.get("", rootPath, pathBackendLoadBalancerSticky)
|
|
if len(stickyValue) > 0 {
|
|
log.Warnf("Deprecated configuration found: %s. Please use %s.", pathBackendLoadBalancerSticky, pathBackendLoadBalancerStickiness)
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
sticky, err := strconv.ParseBool(stickyValue)
|
|
if err != nil {
|
|
log.Warnf("Invalid %s value: %s.", pathBackendLoadBalancerSticky, stickyValue)
|
|
}
|
|
|
|
return sticky
|
|
}
|
|
|
|
// Deprecated
|
|
func (p *Provider) hasStickinessLabel(rootPath string) bool {
|
|
return p.getBool(false, rootPath, pathBackendLoadBalancerStickiness)
|
|
}
|
|
|
|
// Deprecated
|
|
func (p *Provider) getStickinessCookieName(rootPath string) string {
|
|
return p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName)
|
|
}
|
|
|
|
func (p *Provider) getWhiteList(rootPath string) *types.WhiteList {
|
|
ranges := p.getList(rootPath, pathFrontendWhiteListSourceRange)
|
|
|
|
if len(ranges) > 0 {
|
|
return &types.WhiteList{
|
|
SourceRange: ranges,
|
|
UseXForwardedFor: p.getBool(false, rootPath, pathFrontendWhiteListUseXForwardedFor),
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Provider) getRedirect(rootPath string) *types.Redirect {
|
|
permanent := p.getBool(false, rootPath, pathFrontendRedirectPermanent)
|
|
|
|
if p.has(rootPath, pathFrontendRedirectEntryPoint) {
|
|
return &types.Redirect{
|
|
EntryPoint: p.get("", rootPath, pathFrontendRedirectEntryPoint),
|
|
Permanent: permanent,
|
|
}
|
|
}
|
|
|
|
if p.has(rootPath, pathFrontendRedirectRegex) && p.has(rootPath, pathFrontendRedirectReplacement) {
|
|
return &types.Redirect{
|
|
Regex: p.get("", rootPath, pathFrontendRedirectRegex),
|
|
Replacement: p.get("", rootPath, pathFrontendRedirectReplacement),
|
|
Permanent: permanent,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Provider) getErrorPages(rootPath string) map[string]*types.ErrorPage {
|
|
var errorPages map[string]*types.ErrorPage
|
|
|
|
pathErrors := p.list(rootPath, pathFrontendErrorPages)
|
|
|
|
for _, pathPage := range pathErrors {
|
|
if errorPages == nil {
|
|
errorPages = make(map[string]*types.ErrorPage)
|
|
}
|
|
|
|
pageName := p.last(pathPage)
|
|
|
|
errorPages[pageName] = &types.ErrorPage{
|
|
Backend: p.get("", pathPage, pathFrontendErrorPagesBackend),
|
|
Query: p.get("", pathPage, pathFrontendErrorPagesQuery),
|
|
Status: p.getList(pathPage, pathFrontendErrorPagesStatus),
|
|
}
|
|
}
|
|
|
|
return errorPages
|
|
}
|
|
|
|
func (p *Provider) getRateLimit(rootPath string) *types.RateLimit {
|
|
extractorFunc := p.get("", rootPath, pathFrontendRateLimitExtractorFunc)
|
|
if len(extractorFunc) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var limits map[string]*types.Rate
|
|
|
|
pathRateSet := p.list(rootPath, pathFrontendRateLimitRateSet)
|
|
for _, pathLimits := range pathRateSet {
|
|
if limits == nil {
|
|
limits = make(map[string]*types.Rate)
|
|
}
|
|
|
|
rawPeriod := p.get("", pathLimits+pathFrontendRateLimitPeriod)
|
|
|
|
var period flaeg.Duration
|
|
err := period.Set(rawPeriod)
|
|
if err != nil {
|
|
log.Errorf("Invalid %q value: %q", pathLimits+pathFrontendRateLimitPeriod, rawPeriod)
|
|
continue
|
|
}
|
|
|
|
limitName := p.last(pathLimits)
|
|
|
|
limits[limitName] = &types.Rate{
|
|
Average: p.getInt64(0, pathLimits+pathFrontendRateLimitAverage),
|
|
Burst: p.getInt64(0, pathLimits+pathFrontendRateLimitBurst),
|
|
Period: period,
|
|
}
|
|
}
|
|
|
|
return &types.RateLimit{
|
|
ExtractorFunc: extractorFunc,
|
|
RateSet: limits,
|
|
}
|
|
}
|
|
|
|
func (p *Provider) getHeaders(rootPath string) *types.Headers {
|
|
headers := &types.Headers{
|
|
CustomRequestHeaders: p.getMap(rootPath, pathFrontendCustomRequestHeaders),
|
|
CustomResponseHeaders: p.getMap(rootPath, pathFrontendCustomResponseHeaders),
|
|
SSLProxyHeaders: p.getMap(rootPath, pathFrontendSSLProxyHeaders),
|
|
AllowedHosts: p.getList("", rootPath, pathFrontendAllowedHosts),
|
|
HostsProxyHeaders: p.getList(rootPath, pathFrontendHostsProxyHeaders),
|
|
SSLForceHost: p.getBool(false, rootPath, pathFrontendSSLForceHost),
|
|
SSLRedirect: p.getBool(false, rootPath, pathFrontendSSLRedirect),
|
|
SSLTemporaryRedirect: p.getBool(false, rootPath, pathFrontendSSLTemporaryRedirect),
|
|
SSLHost: p.get("", rootPath, pathFrontendSSLHost),
|
|
STSSeconds: p.getInt64(0, rootPath, pathFrontendSTSSeconds),
|
|
STSIncludeSubdomains: p.getBool(false, rootPath, pathFrontendSTSIncludeSubdomains),
|
|
STSPreload: p.getBool(false, rootPath, pathFrontendSTSPreload),
|
|
ForceSTSHeader: p.getBool(false, rootPath, pathFrontendForceSTSHeader),
|
|
FrameDeny: p.getBool(false, rootPath, pathFrontendFrameDeny),
|
|
CustomFrameOptionsValue: p.get("", rootPath, pathFrontendCustomFrameOptionsValue),
|
|
ContentTypeNosniff: p.getBool(false, rootPath, pathFrontendContentTypeNosniff),
|
|
BrowserXSSFilter: p.getBool(false, rootPath, pathFrontendBrowserXSSFilter),
|
|
CustomBrowserXSSValue: p.get("", rootPath, pathFrontendCustomBrowserXSSValue),
|
|
ContentSecurityPolicy: p.get("", rootPath, pathFrontendContentSecurityPolicy),
|
|
PublicKey: p.get("", rootPath, pathFrontendPublicKey),
|
|
ReferrerPolicy: p.get("", rootPath, pathFrontendReferrerPolicy),
|
|
IsDevelopment: p.getBool(false, rootPath, pathFrontendIsDevelopment),
|
|
}
|
|
|
|
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
|
|
return nil
|
|
}
|
|
|
|
return headers
|
|
}
|
|
|
|
func (p *Provider) getLoadBalancer(rootPath string) *types.LoadBalancer {
|
|
lb := &types.LoadBalancer{
|
|
Method: p.get(label.DefaultBackendLoadBalancerMethod, rootPath, pathBackendLoadBalancerMethod),
|
|
Sticky: p.getSticky(rootPath),
|
|
}
|
|
|
|
if p.getBool(false, rootPath, pathBackendLoadBalancerStickiness) {
|
|
lb.Stickiness = &types.Stickiness{
|
|
CookieName: p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName),
|
|
}
|
|
}
|
|
|
|
return lb
|
|
}
|
|
|
|
func (p *Provider) getCircuitBreaker(rootPath string) *types.CircuitBreaker {
|
|
if !p.has(rootPath, pathBackendCircuitBreakerExpression) {
|
|
return nil
|
|
}
|
|
|
|
circuitBreaker := p.get(label.DefaultCircuitBreakerExpression, rootPath, pathBackendCircuitBreakerExpression)
|
|
if len(circuitBreaker) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return &types.CircuitBreaker{Expression: circuitBreaker}
|
|
}
|
|
|
|
func (p *Provider) getMaxConn(rootPath string) *types.MaxConn {
|
|
amount := p.getInt64(math.MinInt64, rootPath, pathBackendMaxConnAmount)
|
|
extractorFunc := p.get(label.DefaultBackendMaxconnExtractorFunc, rootPath, pathBackendMaxConnExtractorFunc)
|
|
|
|
if amount == math.MinInt64 || len(extractorFunc) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return &types.MaxConn{
|
|
Amount: amount,
|
|
ExtractorFunc: extractorFunc,
|
|
}
|
|
}
|
|
|
|
func (p *Provider) getHealthCheck(rootPath string) *types.HealthCheck {
|
|
path := p.get("", rootPath, pathBackendHealthCheckPath)
|
|
|
|
if len(path) == 0 {
|
|
return nil
|
|
}
|
|
|
|
scheme := p.get("", rootPath, pathBackendHealthCheckScheme)
|
|
port := p.getInt(label.DefaultBackendHealthCheckPort, rootPath, pathBackendHealthCheckPort)
|
|
interval := p.get("30s", rootPath, pathBackendHealthCheckInterval)
|
|
hostname := p.get("", rootPath, pathBackendHealthCheckHostname)
|
|
headers := p.getMap(rootPath, pathBackendHealthCheckHeaders)
|
|
|
|
return &types.HealthCheck{
|
|
Scheme: scheme,
|
|
Path: path,
|
|
Port: port,
|
|
Interval: interval,
|
|
Hostname: hostname,
|
|
Headers: headers,
|
|
}
|
|
}
|
|
|
|
func (p *Provider) getBuffering(rootPath string) *types.Buffering {
|
|
pathsBuffering := p.list(rootPath, pathBackendBuffering)
|
|
|
|
var buffering *types.Buffering
|
|
if len(pathsBuffering) > 0 {
|
|
if buffering == nil {
|
|
buffering = &types.Buffering{}
|
|
}
|
|
|
|
buffering.MaxRequestBodyBytes = p.getInt64(0, rootPath, pathBackendBufferingMaxRequestBodyBytes)
|
|
buffering.MaxResponseBodyBytes = p.getInt64(0, rootPath, pathBackendBufferingMaxResponseBodyBytes)
|
|
buffering.MemRequestBodyBytes = p.getInt64(0, rootPath, pathBackendBufferingMemRequestBodyBytes)
|
|
buffering.MemResponseBodyBytes = p.getInt64(0, rootPath, pathBackendBufferingMemResponseBodyBytes)
|
|
buffering.RetryExpression = p.get("", rootPath, pathBackendBufferingRetryExpression)
|
|
}
|
|
|
|
return buffering
|
|
}
|
|
|
|
func (p *Provider) getTLSSection(prefix string) []*tls.Configuration {
|
|
var tlsSection []*tls.Configuration
|
|
|
|
for _, tlsConfPath := range p.list(prefix, pathTLS) {
|
|
certFile := p.get("", tlsConfPath, pathTLSCertFile)
|
|
keyFile := p.get("", tlsConfPath, pathTLSKeyFile)
|
|
|
|
if len(certFile) == 0 && len(keyFile) == 0 {
|
|
log.Warnf("Invalid TLS configuration (no cert and no key): %s", tlsConfPath)
|
|
continue
|
|
}
|
|
|
|
entryPoints := p.getList(tlsConfPath, pathTLSEntryPoints)
|
|
if len(entryPoints) == 0 {
|
|
log.Warnf("Invalid TLS configuration (no entry points): %s", tlsConfPath)
|
|
continue
|
|
}
|
|
|
|
tlsConf := &tls.Configuration{
|
|
EntryPoints: entryPoints,
|
|
Certificate: &tls.Certificate{
|
|
CertFile: tls.FileOrContent(certFile),
|
|
KeyFile: tls.FileOrContent(keyFile),
|
|
},
|
|
}
|
|
|
|
tlsSection = append(tlsSection, tlsConf)
|
|
}
|
|
|
|
return tlsSection
|
|
}
|
|
|
|
func (p *Provider) getRoutes(rootPath string) map[string]types.Route {
|
|
var routes map[string]types.Route
|
|
|
|
rts := p.list(rootPath, pathFrontendRoutes)
|
|
for _, rt := range rts {
|
|
|
|
rule := p.get("", rt, pathFrontendRule)
|
|
if len(rule) == 0 {
|
|
continue
|
|
}
|
|
|
|
if routes == nil {
|
|
routes = make(map[string]types.Route)
|
|
}
|
|
|
|
routeName := p.last(rt)
|
|
routes[routeName] = types.Route{
|
|
Rule: rule,
|
|
}
|
|
}
|
|
|
|
return routes
|
|
}
|
|
|
|
func (p *Provider) getServers(rootPath string) map[string]types.Server {
|
|
var servers map[string]types.Server
|
|
|
|
serverKeys := p.listServers(rootPath)
|
|
for _, serverKey := range serverKeys {
|
|
serverURL := p.get("", serverKey, pathBackendServerURL)
|
|
if len(serverURL) == 0 {
|
|
continue
|
|
}
|
|
|
|
if servers == nil {
|
|
servers = make(map[string]types.Server)
|
|
}
|
|
|
|
serverName := p.last(serverKey)
|
|
servers[serverName] = types.Server{
|
|
URL: serverURL,
|
|
Weight: p.getInt(label.DefaultWeight, serverKey, pathBackendServerWeight),
|
|
}
|
|
}
|
|
|
|
return servers
|
|
}
|
|
|
|
func (p *Provider) listServers(backend string) []string {
|
|
serverNames := p.list(backend, pathBackendServers)
|
|
return fun.Filter(p.serverFilter, serverNames).([]string)
|
|
}
|
|
|
|
func (p *Provider) serverFilter(serverName string) bool {
|
|
key := fmt.Sprint(serverName, pathBackendServerURL)
|
|
if _, err := p.kvClient.Get(key, nil); err != nil {
|
|
if err != store.ErrKeyNotFound {
|
|
log.Errorf("Failed to retrieve value for key %s: %s", key, err)
|
|
}
|
|
return false
|
|
}
|
|
return p.checkConstraints(serverName, pathTags)
|
|
}
|
|
|
|
func (p *Provider) checkConstraints(keys ...string) bool {
|
|
joinedKeys := strings.Join(keys, "")
|
|
keyPair, err := p.kvClient.Get(joinedKeys, nil)
|
|
|
|
value := ""
|
|
if err == nil && keyPair != nil && keyPair.Value != nil {
|
|
value = string(keyPair.Value)
|
|
}
|
|
|
|
constraintTags := label.SplitAndTrimString(value, ",")
|
|
ok, failingConstraint := p.MatchConstraints(constraintTags)
|
|
if !ok {
|
|
if failingConstraint != nil {
|
|
log.Debugf("Constraint %v not matching with following tags: %v", failingConstraint.String(), value)
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p *Provider) getFuncString(key string, defaultValue string) func(rootPath string) string {
|
|
return func(rootPath string) string {
|
|
return p.get(defaultValue, rootPath, key)
|
|
}
|
|
}
|
|
|
|
func (p *Provider) getFuncBool(key string, defaultValue bool) func(rootPath string) bool {
|
|
return func(rootPath string) bool {
|
|
return p.getBool(defaultValue, rootPath, key)
|
|
}
|
|
}
|
|
|
|
func (p *Provider) getFuncInt(key string, defaultValue int) func(rootPath string) int {
|
|
return func(rootPath string) int {
|
|
return p.getInt(defaultValue, rootPath, key)
|
|
}
|
|
}
|
|
|
|
func (p *Provider) getFuncList(key string) func(rootPath string) []string {
|
|
return func(rootPath string) []string {
|
|
return p.getList(rootPath, key)
|
|
}
|
|
}
|
|
|
|
func (p *Provider) get(defaultValue string, keyParts ...string) string {
|
|
key := strings.Join(keyParts, "")
|
|
|
|
if p.storeType == store.ETCD {
|
|
key = strings.TrimPrefix(key, pathSeparator)
|
|
}
|
|
|
|
keyPair, err := p.kvClient.Get(key, nil)
|
|
if err != nil {
|
|
log.Debugf("Cannot get key %s %s, setting default %s", key, err, defaultValue)
|
|
return defaultValue
|
|
} else if keyPair == nil {
|
|
log.Debugf("Cannot get key %s, setting default %s", key, defaultValue)
|
|
return defaultValue
|
|
}
|
|
|
|
return string(keyPair.Value)
|
|
}
|
|
|
|
func (p *Provider) getBool(defaultValue bool, keyParts ...string) bool {
|
|
rawValue := p.get(strconv.FormatBool(defaultValue), keyParts...)
|
|
|
|
if len(rawValue) == 0 {
|
|
return defaultValue
|
|
}
|
|
|
|
value, err := strconv.ParseBool(rawValue)
|
|
if err != nil {
|
|
log.Errorf("Invalid value for %v: %s", keyParts, rawValue)
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (p *Provider) has(keyParts ...string) bool {
|
|
value := p.get("", keyParts...)
|
|
return len(value) > 0
|
|
}
|
|
|
|
func (p *Provider) getInt(defaultValue int, keyParts ...string) int {
|
|
rawValue := p.get("", keyParts...)
|
|
|
|
if len(rawValue) == 0 {
|
|
return defaultValue
|
|
}
|
|
|
|
value, err := strconv.Atoi(rawValue)
|
|
if err != nil {
|
|
log.Errorf("Invalid value for %v: %s", keyParts, rawValue)
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (p *Provider) getInt64(defaultValue int64, keyParts ...string) int64 {
|
|
rawValue := p.get("", keyParts...)
|
|
|
|
if len(rawValue) == 0 {
|
|
return defaultValue
|
|
}
|
|
|
|
value, err := strconv.ParseInt(rawValue, 10, 64)
|
|
if err != nil {
|
|
log.Errorf("Invalid value for %v: %s", keyParts, rawValue)
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (p *Provider) list(keyParts ...string) []string {
|
|
rootKey := strings.Join(keyParts, "")
|
|
|
|
keysPairs, err := p.kvClient.List(rootKey, nil)
|
|
if err != nil {
|
|
log.Debugf("Cannot list keys under %q: %v", rootKey, err)
|
|
return nil
|
|
}
|
|
|
|
directoryKeys := make(map[string]string)
|
|
for _, key := range keysPairs {
|
|
directory := strings.Split(strings.TrimPrefix(key.Key, rootKey), pathSeparator)[0]
|
|
directoryKeys[directory] = rootKey + directory
|
|
}
|
|
|
|
keys := fun.Values(directoryKeys).([]string)
|
|
sort.Strings(keys)
|
|
return keys
|
|
}
|
|
|
|
func (p *Provider) getList(keyParts ...string) []string {
|
|
values := p.splitGet(keyParts...)
|
|
if len(values) > 0 {
|
|
return values
|
|
}
|
|
|
|
return p.getSlice(keyParts...)
|
|
}
|
|
|
|
// get sub keys. ex: foo/0, foo/1, foo/2
|
|
func (p *Provider) getSlice(keyParts ...string) []string {
|
|
baseKey := strings.Join(keyParts, "")
|
|
if !strings.HasSuffix(baseKey, "/") {
|
|
baseKey += "/"
|
|
}
|
|
|
|
listKeys := p.list(baseKey)
|
|
|
|
var values []string
|
|
for _, entryKey := range listKeys {
|
|
val := p.get("", entryKey)
|
|
if len(val) > 0 {
|
|
values = append(values, val)
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
|
|
func (p *Provider) splitGet(keyParts ...string) []string {
|
|
value := p.get("", keyParts...)
|
|
|
|
if len(value) == 0 {
|
|
return nil
|
|
}
|
|
return label.SplitAndTrimString(value, ",")
|
|
}
|
|
|
|
func (p *Provider) last(key string) string {
|
|
index := strings.LastIndex(key, pathSeparator)
|
|
return key[index+1:]
|
|
}
|
|
|
|
func (p *Provider) getMap(keyParts ...string) map[string]string {
|
|
var mapData map[string]string
|
|
|
|
list := p.list(keyParts...)
|
|
for _, name := range list {
|
|
if mapData == nil {
|
|
mapData = make(map[string]string)
|
|
}
|
|
|
|
mapData[http.CanonicalHeaderKey(p.last(name))] = p.get("", name)
|
|
}
|
|
|
|
return mapData
|
|
}
|