Vendor integration dependencies.
This commit is contained in:
parent
dd5e3fba01
commit
55b57c736b
2451 changed files with 731611 additions and 0 deletions
34
integration/vendor/github.com/containous/traefik/provider/boltdb.go
generated
vendored
Normal file
34
integration/vendor/github.com/containous/traefik/provider/boltdb.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/boltdb"
|
||||
)
|
||||
|
||||
var _ Provider = (*BoltDb)(nil)
|
||||
|
||||
// BoltDb holds configurations of the BoltDb provider.
|
||||
type BoltDb struct {
|
||||
Kv `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
provider.kvclient = store
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
||||
// CreateStore creates the KV store
|
||||
func (provider *BoltDb) CreateStore() (store.Store, error) {
|
||||
provider.storeType = store.BOLTDB
|
||||
boltdb.Register()
|
||||
return provider.createStore()
|
||||
}
|
34
integration/vendor/github.com/containous/traefik/provider/consul.go
generated
vendored
Normal file
34
integration/vendor/github.com/containous/traefik/provider/consul.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/consul"
|
||||
)
|
||||
|
||||
var _ Provider = (*Consul)(nil)
|
||||
|
||||
// Consul holds configurations of the Consul provider.
|
||||
type Consul struct {
|
||||
Kv `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
provider.kvclient = store
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
||||
// CreateStore creates the KV store
|
||||
func (provider *Consul) CreateStore() (store.Store, error) {
|
||||
provider.storeType = store.CONSUL
|
||||
consul.Register()
|
||||
return provider.createStore()
|
||||
}
|
344
integration/vendor/github.com/containous/traefik/provider/consul_catalog.go
generated
vendored
Normal file
344
integration/vendor/github.com/containous/traefik/provider/consul_catalog.go
generated
vendored
Normal file
|
@ -0,0 +1,344 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultWatchWaitTime is the duration to wait when polling consul
|
||||
DefaultWatchWaitTime = 15 * time.Second
|
||||
// DefaultConsulCatalogTagPrefix is a prefix for additional service/node configurations
|
||||
DefaultConsulCatalogTagPrefix = "traefik"
|
||||
)
|
||||
|
||||
var _ Provider = (*ConsulCatalog)(nil)
|
||||
|
||||
// ConsulCatalog holds configurations of the Consul catalog provider.
|
||||
type ConsulCatalog struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string `description:"Consul server endpoint"`
|
||||
Domain string `description:"Default domain used"`
|
||||
client *api.Client
|
||||
Prefix string
|
||||
}
|
||||
|
||||
type serviceUpdate struct {
|
||||
ServiceName string
|
||||
Attributes []string
|
||||
}
|
||||
|
||||
type catalogUpdate struct {
|
||||
Service *serviceUpdate
|
||||
Nodes []*api.ServiceEntry
|
||||
}
|
||||
|
||||
type nodeSorter []*api.ServiceEntry
|
||||
|
||||
func (a nodeSorter) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
func (a nodeSorter) Swap(i int, j int) {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
|
||||
func (a nodeSorter) Less(i int, j int) bool {
|
||||
lentr := a[i]
|
||||
rentr := a[j]
|
||||
|
||||
ls := strings.ToLower(lentr.Service.Service)
|
||||
lr := strings.ToLower(rentr.Service.Service)
|
||||
|
||||
if ls != lr {
|
||||
return ls < lr
|
||||
}
|
||||
if lentr.Service.Address != rentr.Service.Address {
|
||||
return lentr.Service.Address < rentr.Service.Address
|
||||
}
|
||||
if lentr.Node.Address != rentr.Node.Address {
|
||||
return lentr.Node.Address < rentr.Node.Address
|
||||
}
|
||||
return lentr.Service.Port < rentr.Service.Port
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string {
|
||||
watchCh := make(chan map[string][]string)
|
||||
|
||||
catalog := provider.client.Catalog()
|
||||
|
||||
safe.Go(func() {
|
||||
defer close(watchCh)
|
||||
|
||||
opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
data, meta, err := catalog.Services(opts)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to list services")
|
||||
return
|
||||
}
|
||||
|
||||
// If LastIndex didn't change then it means `Get` returned
|
||||
// because of the WaitTime and the key didn't changed.
|
||||
if opts.WaitIndex == meta.LastIndex {
|
||||
continue
|
||||
}
|
||||
opts.WaitIndex = meta.LastIndex
|
||||
|
||||
if data != nil {
|
||||
watchCh <- data
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return watchCh
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, error) {
|
||||
health := provider.client.Health()
|
||||
opts := &api.QueryOptions{}
|
||||
data, _, err := health.Service(service, "", true, opts)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to fetch details of " + service)
|
||||
return catalogUpdate{}, err
|
||||
}
|
||||
|
||||
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
|
||||
constraintTags := provider.getContraintTags(node.Service.Tags)
|
||||
ok, failingConstraint := provider.MatchConstraints(constraintTags)
|
||||
if ok == false && failingConstraint != nil {
|
||||
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
|
||||
}
|
||||
return ok
|
||||
}, data).([]*api.ServiceEntry)
|
||||
|
||||
//Merge tags of nodes matching constraints, in a single slice.
|
||||
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
|
||||
return fun.Keys(fun.Union(
|
||||
fun.Set(set),
|
||||
fun.Set(node.Service.Tags),
|
||||
).(map[string]bool)).([]string)
|
||||
}, []string{}, nodes).([]string)
|
||||
|
||||
return catalogUpdate{
|
||||
Service: &serviceUpdate{
|
||||
ServiceName: service,
|
||||
Attributes: tags,
|
||||
},
|
||||
Nodes: nodes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getEntryPoints(list string) []string {
|
||||
return strings.Split(list, ",")
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getBackend(node *api.ServiceEntry) string {
|
||||
return strings.ToLower(node.Service.Service)
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getFrontendRule(service serviceUpdate) string {
|
||||
customFrontendRule := provider.getAttribute("frontend.rule", service.Attributes, "")
|
||||
if customFrontendRule != "" {
|
||||
return customFrontendRule
|
||||
}
|
||||
return "Host:" + service.ServiceName + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getBackendAddress(node *api.ServiceEntry) string {
|
||||
if node.Service.Address != "" {
|
||||
return node.Service.Address
|
||||
}
|
||||
return node.Node.Address
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getBackendName(node *api.ServiceEntry, index int) string {
|
||||
serviceName := strings.ToLower(node.Service.Service) + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port)
|
||||
|
||||
for _, tag := range node.Service.Tags {
|
||||
serviceName += "--" + normalize(tag)
|
||||
}
|
||||
|
||||
serviceName = strings.Replace(serviceName, ".", "-", -1)
|
||||
serviceName = strings.Replace(serviceName, "=", "-", -1)
|
||||
|
||||
// unique int at the end
|
||||
serviceName += "--" + strconv.Itoa(index)
|
||||
return serviceName
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultValue string) string {
|
||||
for _, tag := range tags {
|
||||
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".") == 0 {
|
||||
if kv := strings.SplitN(tag[len(DefaultConsulCatalogTagPrefix+"."):], "=", 2); len(kv) == 2 && strings.ToLower(kv[0]) == strings.ToLower(name) {
|
||||
return kv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getContraintTags(tags []string) []string {
|
||||
var list []string
|
||||
|
||||
for _, tag := range tags {
|
||||
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".tags=") == 0 {
|
||||
splitedTags := strings.Split(tag[len(DefaultConsulCatalogTagPrefix+".tags="):], ",")
|
||||
list = append(list, splitedTags...)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getBackendName": provider.getBackendName,
|
||||
"getBackendAddress": provider.getBackendAddress,
|
||||
"getAttribute": provider.getAttribute,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"hasMaxconnAttributes": provider.hasMaxconnAttributes,
|
||||
}
|
||||
|
||||
allNodes := []*api.ServiceEntry{}
|
||||
services := []*serviceUpdate{}
|
||||
for _, info := range catalog {
|
||||
for _, node := range info.Nodes {
|
||||
isEnabled := provider.getAttribute("enable", node.Service.Tags, "true")
|
||||
if isEnabled != "false" && len(info.Nodes) > 0 {
|
||||
services = append(services, info.Service)
|
||||
allNodes = append(allNodes, info.Nodes...)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// Ensure a stable ordering of nodes so that identical configurations may be detected
|
||||
sort.Sort(nodeSorter(allNodes))
|
||||
|
||||
templateObjects := struct {
|
||||
Services []*serviceUpdate
|
||||
Nodes []*api.ServiceEntry
|
||||
}{
|
||||
Services: services,
|
||||
Nodes: allNodes,
|
||||
}
|
||||
|
||||
configuration, err := provider.getConfiguration("templates/consul_catalog.tmpl", FuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create config")
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) hasMaxconnAttributes(attributes []string) bool {
|
||||
amount := provider.getAttribute("backend.maxconn.amount", attributes, "")
|
||||
extractorfunc := provider.getAttribute("backend.maxconn.extractorfunc", attributes, "")
|
||||
if amount != "" && extractorfunc != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpdate, error) {
|
||||
visited := make(map[string]bool)
|
||||
|
||||
nodes := []catalogUpdate{}
|
||||
for service := range index {
|
||||
name := strings.ToLower(service)
|
||||
if !strings.Contains(name, " ") && !visited[name] {
|
||||
visited[name] = true
|
||||
log.WithFields(logrus.Fields{
|
||||
"service": name,
|
||||
}).Debug("Fetching service")
|
||||
healthy, err := provider.healthyNodes(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// healthy.Nodes can be empty if constraints do not match, without throwing error
|
||||
if healthy.Service != nil && len(healthy.Nodes) > 0 {
|
||||
nodes = append(nodes, healthy)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessage, stop chan bool) error {
|
||||
stopCh := make(chan struct{})
|
||||
serviceCatalog := provider.watchServices(stopCh)
|
||||
|
||||
defer close(stopCh)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return nil
|
||||
case index, ok := <-serviceCatalog:
|
||||
if !ok {
|
||||
return errors.New("Consul service list nil")
|
||||
}
|
||||
log.Debug("List of services changed")
|
||||
nodes, err := provider.getNodes(index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configuration := provider.buildConfig(nodes)
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "consul_catalog",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
config := api.DefaultConfig()
|
||||
config.Address = provider.Endpoint
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
provider.client = client
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
|
||||
pool.Go(func(stop chan bool) {
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Consul connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
operation := func() error {
|
||||
return provider.watch(configurationChan, stop)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to consul server %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
663
integration/vendor/github.com/containous/traefik/provider/docker.go
generated
vendored
Normal file
663
integration/vendor/github.com/containous/traefik/provider/docker.go
generated
vendored
Normal file
|
@ -0,0 +1,663 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/docker/engine-api/client"
|
||||
dockertypes "github.com/docker/engine-api/types"
|
||||
dockercontainertypes "github.com/docker/engine-api/types/container"
|
||||
eventtypes "github.com/docker/engine-api/types/events"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
swarmtypes "github.com/docker/engine-api/types/swarm"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/vdemeester/docker-events"
|
||||
)
|
||||
|
||||
const (
|
||||
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
|
||||
DockerAPIVersion string = "1.21"
|
||||
// SwarmAPIVersion is a constant holding the version of the Docker API traefik will use
|
||||
SwarmAPIVersion string = "1.24"
|
||||
// SwarmDefaultWatchTime is the duration of the interval when polling docker
|
||||
SwarmDefaultWatchTime = 15 * time.Second
|
||||
)
|
||||
|
||||
var _ Provider = (*Docker)(nil)
|
||||
|
||||
// Docker holds configurations of the Docker provider.
|
||||
type Docker struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
|
||||
Domain string `description:"Default domain used"`
|
||||
TLS *ClientTLS `description:"Enable Docker TLS support"`
|
||||
ExposedByDefault bool `description:"Expose containers by default"`
|
||||
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network"`
|
||||
SwarmMode bool `description:"Use Docker on Swarm Mode"`
|
||||
}
|
||||
|
||||
// dockerData holds the need data to the Docker provider
|
||||
type dockerData struct {
|
||||
Name string
|
||||
Labels map[string]string // List of labels set to container or service
|
||||
NetworkSettings networkSettings
|
||||
Health string
|
||||
}
|
||||
|
||||
// NetworkSettings holds the networks data to the Docker provider
|
||||
type networkSettings struct {
|
||||
NetworkMode dockercontainertypes.NetworkMode
|
||||
Ports nat.PortMap
|
||||
Networks map[string]*networkData
|
||||
}
|
||||
|
||||
// Network holds the network data to the Docker provider
|
||||
type networkData struct {
|
||||
Name string
|
||||
Addr string
|
||||
Port int
|
||||
Protocol string
|
||||
ID string
|
||||
}
|
||||
|
||||
func (provider *Docker) createClient() (client.APIClient, error) {
|
||||
var httpClient *http.Client
|
||||
httpHeaders := map[string]string{
|
||||
"User-Agent": "Traefik " + version.Version,
|
||||
}
|
||||
if provider.TLS != nil {
|
||||
config, err := provider.TLS.CreateTLSConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: config,
|
||||
}
|
||||
proto, addr, _, err := client.ParseHost(provider.Endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sockets.ConfigureTransport(tr, proto, addr)
|
||||
|
||||
httpClient = &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
}
|
||||
var version string
|
||||
if provider.SwarmMode {
|
||||
version = SwarmAPIVersion
|
||||
} else {
|
||||
version = DockerAPIVersion
|
||||
}
|
||||
return client.NewClient(provider.Endpoint, version, httpClient, httpHeaders)
|
||||
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
// TODO register this routine in pool, and watch for stop channel
|
||||
safe.Go(func() {
|
||||
operation := func() error {
|
||||
var err error
|
||||
|
||||
dockerClient, err := provider.createClient()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for docker, error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
version, err := dockerClient.ServerVersion(ctx)
|
||||
log.Debugf("Docker connection established with docker %s (API %s)", version.Version, version.APIVersion)
|
||||
var dockerDataList []dockerData
|
||||
if provider.SwarmMode {
|
||||
dockerDataList, err = listServices(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list services for docker swarm mode, error %s", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dockerDataList, err = listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list containers for docker, error %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
configuration := provider.loadDockerConfig(dockerDataList)
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
if provider.Watch {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
if provider.SwarmMode {
|
||||
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
|
||||
ticker := time.NewTicker(SwarmDefaultWatchTime)
|
||||
pool.Go(func(stop chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
services, err := listServices(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list services for docker, error %s", err)
|
||||
return
|
||||
}
|
||||
configuration := provider.loadDockerConfig(services)
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
|
||||
case <-stop:
|
||||
ticker.Stop()
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
} else {
|
||||
pool.Go(func(stop chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
f := filters.NewArgs()
|
||||
f.Add("type", "container")
|
||||
options := dockertypes.EventsOptions{
|
||||
Filters: f,
|
||||
}
|
||||
eventHandler := events.NewHandler(events.ByAction)
|
||||
startStopHandle := func(m eventtypes.Message) {
|
||||
log.Debugf("Docker event received %+v", m)
|
||||
containers, err := listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list containers for docker, error %s", err)
|
||||
// Call cancel to get out of the monitor
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
configuration := provider.loadDockerConfig(containers)
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
eventHandler.Handle("start", startStopHandle)
|
||||
eventHandler.Handle("die", startStopHandle)
|
||||
eventHandler.Handle("health_status: healthy", startStopHandle)
|
||||
eventHandler.Handle("health_status: unhealthy", startStopHandle)
|
||||
eventHandler.Handle("health_status: starting", startStopHandle)
|
||||
|
||||
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
|
||||
if err := <-errChan; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to docker server %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Docker) loadDockerConfig(containersInspected []dockerData) *types.Configuration {
|
||||
var DockerFuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getIPAddress": provider.getIPAddress,
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"hasCircuitBreakerLabel": provider.hasCircuitBreakerLabel,
|
||||
"getCircuitBreakerExpression": provider.getCircuitBreakerExpression,
|
||||
"hasLoadBalancerLabel": provider.hasLoadBalancerLabel,
|
||||
"getLoadBalancerMethod": provider.getLoadBalancerMethod,
|
||||
"hasMaxConnLabels": provider.hasMaxConnLabels,
|
||||
"getMaxConnAmount": provider.getMaxConnAmount,
|
||||
"getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc,
|
||||
"getSticky": provider.getSticky,
|
||||
"replace": replace,
|
||||
}
|
||||
|
||||
// filter containers
|
||||
filteredContainers := fun.Filter(func(container dockerData) bool {
|
||||
return provider.containerFilter(container)
|
||||
}, containersInspected).([]dockerData)
|
||||
|
||||
frontends := map[string][]dockerData{}
|
||||
backends := map[string]dockerData{}
|
||||
servers := map[string][]dockerData{}
|
||||
for _, container := range filteredContainers {
|
||||
frontendName := provider.getFrontendName(container)
|
||||
frontends[frontendName] = append(frontends[frontendName], container)
|
||||
backendName := provider.getBackend(container)
|
||||
backends[backendName] = container
|
||||
servers[backendName] = append(servers[backendName], container)
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
Containers []dockerData
|
||||
Frontends map[string][]dockerData
|
||||
Backends map[string]dockerData
|
||||
Servers map[string][]dockerData
|
||||
Domain string
|
||||
}{
|
||||
filteredContainers,
|
||||
frontends,
|
||||
backends,
|
||||
servers,
|
||||
provider.Domain,
|
||||
}
|
||||
|
||||
configuration, err := provider.getConfiguration("templates/docker.tmpl", DockerFuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
func (provider *Docker) hasCircuitBreakerLabel(container dockerData) bool {
|
||||
if _, err := getLabel(container, "traefik.backend.circuitbreaker.expression"); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Docker) hasLoadBalancerLabel(container dockerData) bool {
|
||||
_, errMethod := getLabel(container, "traefik.backend.loadbalancer.method")
|
||||
_, errSticky := getLabel(container, "traefik.backend.loadbalancer.sticky")
|
||||
if errMethod != nil && errSticky != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Docker) hasMaxConnLabels(container dockerData) bool {
|
||||
if _, err := getLabel(container, "traefik.backend.maxconn.amount"); err != nil {
|
||||
return false
|
||||
}
|
||||
if _, err := getLabel(container, "traefik.backend.maxconn.extractorfunc"); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Docker) getCircuitBreakerExpression(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.backend.circuitbreaker.expression"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "NetworkErrorRatio() > 1"
|
||||
}
|
||||
|
||||
func (provider *Docker) getLoadBalancerMethod(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.backend.loadbalancer.method"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "wrr"
|
||||
}
|
||||
|
||||
func (provider *Docker) getMaxConnAmount(container dockerData) int64 {
|
||||
if label, err := getLabel(container, "traefik.backend.maxconn.amount"); err == nil {
|
||||
i, errConv := strconv.ParseInt(label, 10, 64)
|
||||
if errConv != nil {
|
||||
log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label)
|
||||
return math.MaxInt64
|
||||
}
|
||||
return i
|
||||
}
|
||||
return math.MaxInt64
|
||||
}
|
||||
|
||||
func (provider *Docker) getMaxConnExtractorFunc(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.backend.maxconn.extractorfunc"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "request.host"
|
||||
}
|
||||
|
||||
func (provider *Docker) containerFilter(container dockerData) bool {
|
||||
_, err := strconv.Atoi(container.Labels["traefik.port"])
|
||||
if len(container.NetworkSettings.Ports) == 0 && err != nil {
|
||||
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
||||
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
if !isContainerEnabled(container, provider.ExposedByDefault) {
|
||||
log.Debugf("Filtering disabled container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
constraintTags := strings.Split(container.Labels["traefik.tags"], ",")
|
||||
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
|
||||
if failingConstraint != nil {
|
||||
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if container.Health != "" && container.Health != "healthy" {
|
||||
log.Debugf("Filtering unhealthy or starting container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Docker) getFrontendName(container dockerData) string {
|
||||
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
|
||||
return normalize(provider.getFrontendRule(container))
|
||||
}
|
||||
|
||||
// GetFrontendRule returns the frontend rule for the specified container, using
|
||||
// it's label. It returns a default one (Host) if the label is not present.
|
||||
func (provider *Docker) getFrontendRule(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Docker) getBackend(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.backend"); err == nil {
|
||||
return label
|
||||
}
|
||||
return normalize(container.Name)
|
||||
}
|
||||
|
||||
func (provider *Docker) getIPAddress(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" {
|
||||
networkSettings := container.NetworkSettings
|
||||
if networkSettings.Networks != nil {
|
||||
network := networkSettings.Networks[label]
|
||||
if network != nil {
|
||||
return network.Addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If net==host, quick n' dirty, we return 127.0.0.1
|
||||
// This will work locally, but will fail with swarm.
|
||||
if "host" == container.NetworkSettings.NetworkMode {
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
if provider.UseBindPortIP {
|
||||
port := provider.getPort(container)
|
||||
for netport, portBindings := range container.NetworkSettings.Ports {
|
||||
if string(netport) == port+"/TCP" || string(netport) == port+"/UDP" {
|
||||
for _, p := range portBindings {
|
||||
return p.HostIP
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, network := range container.NetworkSettings.Networks {
|
||||
return network.Addr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (provider *Docker) getPort(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.port"); err == nil {
|
||||
return label
|
||||
}
|
||||
for key := range container.NetworkSettings.Ports {
|
||||
return key.Port()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (provider *Docker) getWeight(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.weight"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "1"
|
||||
}
|
||||
|
||||
func (provider *Docker) getSticky(container dockerData) string {
|
||||
if _, err := getLabel(container, "traefik.backend.loadbalancer.sticky"); err == nil {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (provider *Docker) getDomain(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.domain"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Docker) getProtocol(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.protocol"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (provider *Docker) getPassHostHeader(container dockerData) string {
|
||||
if passHostHeader, err := getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "true"
|
||||
}
|
||||
|
||||
func (provider *Docker) getPriority(container dockerData) string {
|
||||
if priority, err := getLabel(container, "traefik.frontend.priority"); err == nil {
|
||||
return priority
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Docker) getEntryPoints(container dockerData) []string {
|
||||
if entryPoints, err := getLabel(container, "traefik.frontend.entryPoints"); err == nil {
|
||||
return strings.Split(entryPoints, ",")
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func isContainerEnabled(container dockerData, exposedByDefault bool) bool {
|
||||
return exposedByDefault && container.Labels["traefik.enable"] != "false" || container.Labels["traefik.enable"] == "true"
|
||||
}
|
||||
|
||||
func getLabel(container dockerData, label string) (string, error) {
|
||||
for key, value := range container.Labels {
|
||||
if key == label {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Label not found:" + label)
|
||||
}
|
||||
|
||||
func getLabels(container dockerData, labels []string) (map[string]string, error) {
|
||||
var globalErr error
|
||||
foundLabels := map[string]string{}
|
||||
for _, label := range labels {
|
||||
foundLabel, err := getLabel(container, label)
|
||||
// Error out only if one of them is defined.
|
||||
if err != nil {
|
||||
globalErr = errors.New("Label not found: " + label)
|
||||
continue
|
||||
}
|
||||
foundLabels[label] = foundLabel
|
||||
|
||||
}
|
||||
return foundLabels, globalErr
|
||||
}
|
||||
|
||||
func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
|
||||
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
|
||||
if err != nil {
|
||||
return []dockerData{}, err
|
||||
}
|
||||
containersInspected := []dockerData{}
|
||||
|
||||
// get inspect containers
|
||||
for _, container := range containerList {
|
||||
containerInspected, err := dockerClient.ContainerInspect(ctx, container.ID)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to inspect container %s, error: %s", container.ID, err)
|
||||
} else {
|
||||
dockerData := parseContainer(containerInspected)
|
||||
containersInspected = append(containersInspected, dockerData)
|
||||
}
|
||||
}
|
||||
return containersInspected, nil
|
||||
}
|
||||
|
||||
func parseContainer(container dockertypes.ContainerJSON) dockerData {
|
||||
dockerData := dockerData{
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
if container.ContainerJSONBase != nil {
|
||||
dockerData.Name = container.ContainerJSONBase.Name
|
||||
|
||||
if container.ContainerJSONBase.HostConfig != nil {
|
||||
dockerData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config != nil && container.Config.Labels != nil {
|
||||
dockerData.Labels = container.Config.Labels
|
||||
}
|
||||
|
||||
if container.NetworkSettings != nil {
|
||||
if container.NetworkSettings.Ports != nil {
|
||||
dockerData.NetworkSettings.Ports = container.NetworkSettings.Ports
|
||||
}
|
||||
if container.NetworkSettings.Networks != nil {
|
||||
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for name, containerNetwork := range container.NetworkSettings.Networks {
|
||||
dockerData.NetworkSettings.Networks[name] = &networkData{
|
||||
ID: containerNetwork.NetworkID,
|
||||
Name: name,
|
||||
Addr: containerNetwork.IPAddress,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if container.State != nil && container.State.Health != nil {
|
||||
dockerData.Health = container.State.Health.Status
|
||||
}
|
||||
|
||||
return dockerData
|
||||
}
|
||||
|
||||
// Escape beginning slash "/", convert all others to dash "-"
|
||||
func (provider *Docker) getSubDomain(name string) string {
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
||||
func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) {
|
||||
serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{})
|
||||
if err != nil {
|
||||
return []dockerData{}, err
|
||||
}
|
||||
networkListArgs := filters.NewArgs()
|
||||
networkListArgs.Add("driver", "overlay")
|
||||
|
||||
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})
|
||||
|
||||
networkMap := make(map[string]*dockertypes.NetworkResource)
|
||||
if err != nil {
|
||||
log.Debug("Failed to network inspect on client for docker, error: %s", err)
|
||||
return []dockerData{}, err
|
||||
}
|
||||
for _, network := range networkList {
|
||||
networkToAdd := network
|
||||
networkMap[network.ID] = &networkToAdd
|
||||
}
|
||||
|
||||
var dockerDataList []dockerData
|
||||
|
||||
for _, service := range serviceList {
|
||||
dockerData := parseService(service, networkMap)
|
||||
|
||||
dockerDataList = append(dockerDataList, dockerData)
|
||||
}
|
||||
return dockerDataList, err
|
||||
|
||||
}
|
||||
|
||||
func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) dockerData {
|
||||
dockerData := dockerData{
|
||||
Name: service.Spec.Annotations.Name,
|
||||
Labels: service.Spec.Annotations.Labels,
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
if service.Spec.EndpointSpec != nil {
|
||||
switch service.Spec.EndpointSpec.Mode {
|
||||
case swarm.ResolutionModeDNSRR:
|
||||
log.Debug("Ignored endpoint-mode not supported, service name: %s", dockerData.Name)
|
||||
case swarm.ResolutionModeVIP:
|
||||
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for _, virtualIP := range service.Endpoint.VirtualIPs {
|
||||
networkService := networkMap[virtualIP.NetworkID]
|
||||
if networkService != nil {
|
||||
ip, _, _ := net.ParseCIDR(virtualIP.Addr)
|
||||
network := &networkData{
|
||||
Name: networkService.Name,
|
||||
ID: virtualIP.NetworkID,
|
||||
Addr: ip.String(),
|
||||
}
|
||||
dockerData.NetworkSettings.Networks[network.Name] = network
|
||||
} else {
|
||||
log.Debug("Network not found, id: %s", virtualIP.NetworkID)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return dockerData
|
||||
}
|
34
integration/vendor/github.com/containous/traefik/provider/etcd.go
generated
vendored
Normal file
34
integration/vendor/github.com/containous/traefik/provider/etcd.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/etcd"
|
||||
)
|
||||
|
||||
var _ Provider = (*Etcd)(nil)
|
||||
|
||||
// Etcd holds configurations of the Etcd provider.
|
||||
type Etcd struct {
|
||||
Kv `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
provider.kvclient = store
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
||||
// CreateStore creates the KV store
|
||||
func (provider *Etcd) CreateStore() (store.Store, error) {
|
||||
provider.storeType = store.ETCD
|
||||
etcd.Register()
|
||||
return provider.createStore()
|
||||
}
|
145
integration/vendor/github.com/containous/traefik/provider/eureka.go
generated
vendored
Normal file
145
integration/vendor/github.com/containous/traefik/provider/eureka.go
generated
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"github.com/ArthurHlt/go-eureka-client/eureka"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Eureka holds configuration of the Eureka provider.
|
||||
type Eureka struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Delay string
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Eureka) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||
|
||||
operation := func() error {
|
||||
configuration, err := provider.buildConfiguration()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to build configuration for Eureka, error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "eureka",
|
||||
Configuration: configuration,
|
||||
}
|
||||
|
||||
var delay time.Duration
|
||||
if len(provider.Delay) > 0 {
|
||||
var err error
|
||||
delay, err = time.ParseDuration(provider.Delay)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to parse delay for Eureka, error: %s", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
delay = time.Second * 30
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(delay)
|
||||
go func() {
|
||||
for t := range ticker.C {
|
||||
|
||||
log.Debug("Refreshing Eureka " + t.String())
|
||||
|
||||
configuration, err := provider.buildConfiguration()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to refresh Eureka configuration, error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "eureka",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Eureka connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to Eureka server %+v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build the configuration from Eureka server
|
||||
func (provider *Eureka) buildConfiguration() (*types.Configuration, error) {
|
||||
var EurekaFuncMap = template.FuncMap{
|
||||
"replace": replace,
|
||||
"tolower": strings.ToLower,
|
||||
"getPort": provider.getPort,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getWeight": provider.getWeight,
|
||||
"getInstanceID": provider.getInstanceID,
|
||||
}
|
||||
|
||||
eureka.GetLogger().SetOutput(ioutil.Discard)
|
||||
|
||||
client := eureka.NewClient([]string{
|
||||
provider.Endpoint,
|
||||
})
|
||||
|
||||
applications, err := client.GetApplications()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
Applications []eureka.Application
|
||||
}{
|
||||
applications.Applications,
|
||||
}
|
||||
|
||||
configuration, err := provider.getConfiguration("templates/eureka.tmpl", EurekaFuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return configuration, nil
|
||||
}
|
||||
|
||||
func (provider *Eureka) getPort(instance eureka.InstanceInfo) string {
|
||||
if instance.SecurePort.Enabled {
|
||||
return strconv.Itoa(instance.SecurePort.Port)
|
||||
}
|
||||
return strconv.Itoa(instance.Port.Port)
|
||||
}
|
||||
|
||||
func (provider *Eureka) getProtocol(instance eureka.InstanceInfo) string {
|
||||
if instance.SecurePort.Enabled {
|
||||
return "https"
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (provider *Eureka) getWeight(instance eureka.InstanceInfo) string {
|
||||
if val, ok := instance.Metadata.Map["traefik.weight"]; ok {
|
||||
return val
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Eureka) getInstanceID(instance eureka.InstanceInfo) string {
|
||||
if val, ok := instance.Metadata.Map["traefik.backend.id"]; ok {
|
||||
return val
|
||||
}
|
||||
return strings.Replace(instance.IpAddr, ".", "-", -1) + "-" + provider.getPort(instance)
|
||||
}
|
84
integration/vendor/github.com/containous/traefik/provider/file.go
generated
vendored
Normal file
84
integration/vendor/github.com/containous/traefik/provider/file.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
)
|
||||
|
||||
var _ Provider = (*File)(nil)
|
||||
|
||||
// File holds configurations of the File provider.
|
||||
type File struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error("Error creating file watcher", err)
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Open(provider.Filename)
|
||||
if err != nil {
|
||||
log.Error("Error opening file", err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if provider.Watch {
|
||||
// Process events
|
||||
pool.Go(func(stop chan bool) {
|
||||
defer watcher.Close()
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case event := <-watcher.Events:
|
||||
if strings.Contains(event.Name, file.Name()) {
|
||||
log.Debug("File event:", event)
|
||||
configuration := provider.loadFileConfig(file.Name())
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "file",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
case error := <-watcher.Errors:
|
||||
log.Error("Watcher event error", error)
|
||||
}
|
||||
}
|
||||
})
|
||||
err = watcher.Add(filepath.Dir(file.Name()))
|
||||
if err != nil {
|
||||
log.Error("Error adding file watcher", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
configuration := provider.loadFileConfig(file.Name())
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "file",
|
||||
Configuration: configuration,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *File) loadFileConfig(filename string) *types.Configuration {
|
||||
configuration := new(types.Configuration)
|
||||
if _, err := toml.DecodeFile(filename, configuration); err != nil {
|
||||
log.Error("Error reading file:", err)
|
||||
return nil
|
||||
}
|
||||
return configuration
|
||||
}
|
296
integration/vendor/github.com/containous/traefik/provider/k8s/client.go
generated
vendored
Normal file
296
integration/vendor/github.com/containous/traefik/provider/k8s/client.go
generated
vendored
Normal file
|
@ -0,0 +1,296 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// APIEndpoint defines the base path for kubernetes API resources.
|
||||
APIEndpoint = "/api/v1"
|
||||
extentionsEndpoint = "/apis/extensions/v1beta1"
|
||||
defaultIngress = "/ingresses"
|
||||
namespaces = "/namespaces/"
|
||||
)
|
||||
|
||||
// Client is a client for the Kubernetes master.
|
||||
type Client interface {
|
||||
GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error)
|
||||
GetService(name, namespace string) (Service, error)
|
||||
GetEndpoints(name, namespace string) (Endpoints, error)
|
||||
WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error)
|
||||
}
|
||||
|
||||
type clientImpl struct {
|
||||
endpointURL string
|
||||
tls *tls.Config
|
||||
token string
|
||||
caCert []byte
|
||||
}
|
||||
|
||||
// NewClient returns a new Kubernetes client.
|
||||
// The provided host is an url (scheme://hostname[:port]) of a
|
||||
// Kubernetes master without any path.
|
||||
// The provided client is an authorized http.Client used to perform requests to the Kubernetes API master.
|
||||
func NewClient(baseURL string, caCert []byte, token string) (Client, error) {
|
||||
validURL, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err)
|
||||
}
|
||||
return &clientImpl{
|
||||
endpointURL: strings.TrimSuffix(validURL.String(), "/"),
|
||||
token: token,
|
||||
caCert: caCert,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeQueryString(baseParams map[string]string, labelSelector string) (string, error) {
|
||||
if labelSelector != "" {
|
||||
baseParams["labelSelector"] = labelSelector
|
||||
}
|
||||
queryData, err := json.Marshal(baseParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(queryData), nil
|
||||
}
|
||||
|
||||
// GetIngresses returns all ingresses in the cluster
|
||||
func (c *clientImpl) GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error) {
|
||||
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||
queryParams := map[string]string{}
|
||||
queryData, err := makeQueryString(queryParams, labelSelector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Had problems constructing query string %s : %v", queryParams, err)
|
||||
}
|
||||
body, err := c.do(c.request(getURL, queryData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create ingresses request: GET %q : %v", getURL, err)
|
||||
}
|
||||
|
||||
var ingressList IngressList
|
||||
if err := json.Unmarshal(body, &ingressList); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode list of ingress resources: %v", err)
|
||||
}
|
||||
ingresses := ingressList.Items[:0]
|
||||
for _, ingress := range ingressList.Items {
|
||||
if predicate(ingress) {
|
||||
ingresses = append(ingresses, ingress)
|
||||
}
|
||||
}
|
||||
return ingresses, nil
|
||||
}
|
||||
|
||||
// WatchIngresses returns all ingresses in the cluster
|
||||
func (c *clientImpl) WatchIngresses(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||
return c.watch(getURL, labelSelector, stopCh)
|
||||
}
|
||||
|
||||
// GetService returns the named service from the named namespace
|
||||
func (c *clientImpl) GetService(name, namespace string) (Service, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name
|
||||
|
||||
body, err := c.do(c.request(getURL, ""))
|
||||
if err != nil {
|
||||
return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
|
||||
}
|
||||
|
||||
var service Service
|
||||
if err := json.Unmarshal(body, &service); err != nil {
|
||||
return Service{}, fmt.Errorf("failed to decode service resource: %v", err)
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// WatchServices returns all services in the cluster
|
||||
func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + "/services"
|
||||
return c.watch(getURL, "", stopCh)
|
||||
}
|
||||
|
||||
// GetEndpoints returns the named Endpoints
|
||||
// Endpoints have the same name as the coresponding service
|
||||
func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name
|
||||
|
||||
body, err := c.do(c.request(getURL, ""))
|
||||
if err != nil {
|
||||
return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err)
|
||||
}
|
||||
|
||||
var endpoints Endpoints
|
||||
if err := json.Unmarshal(body, &endpoints); err != nil {
|
||||
return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err)
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// WatchEndpoints returns endpoints in the cluster
|
||||
func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + "/endpoints"
|
||||
return c.watch(getURL, "", stopCh)
|
||||
}
|
||||
|
||||
// WatchAll returns events in the cluster
|
||||
func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
watchCh := make(chan interface{}, 10)
|
||||
errCh := make(chan error, 10)
|
||||
|
||||
stopIngresses := make(chan bool)
|
||||
chanIngresses, chanIngressesErr, err := c.WatchIngresses(labelSelector, stopIngresses)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopServices := make(chan bool)
|
||||
chanServices, chanServicesErr, err := c.WatchServices(stopServices)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopEndpoints := make(chan bool)
|
||||
chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
go func() {
|
||||
defer close(watchCh)
|
||||
defer close(errCh)
|
||||
defer close(stopIngresses)
|
||||
defer close(stopServices)
|
||||
defer close(stopEndpoints)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
stopIngresses <- true
|
||||
stopServices <- true
|
||||
stopEndpoints <- true
|
||||
return
|
||||
case err := <-chanIngressesErr:
|
||||
errCh <- err
|
||||
case err := <-chanServicesErr:
|
||||
errCh <- err
|
||||
case err := <-chanEndpointsErr:
|
||||
errCh <- err
|
||||
case event := <-chanIngresses:
|
||||
watchCh <- event
|
||||
case event := <-chanServices:
|
||||
watchCh <- event
|
||||
case event := <-chanEndpoints:
|
||||
watchCh <- event
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return watchCh, errCh, nil
|
||||
}
|
||||
|
||||
func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
|
||||
res, body, errs := request.EndBytes()
|
||||
if errs != nil {
|
||||
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body))
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (c *clientImpl) request(reqURL string, queryContent interface{}) *gorequest.SuperAgent {
|
||||
// Make request to Kubernetes API
|
||||
parsedURL, parseErr := url.Parse(reqURL)
|
||||
if parseErr != nil {
|
||||
log.Errorf("Had issues parsing url %s. Trying anyway.", reqURL)
|
||||
}
|
||||
request := gorequest.New().Get(reqURL)
|
||||
request.Transport.DisableKeepAlives = true
|
||||
|
||||
if parsedURL.Scheme == "https" {
|
||||
pool := x509.NewCertPool()
|
||||
pool.AppendCertsFromPEM(c.caCert)
|
||||
c.tls = &tls.Config{RootCAs: pool}
|
||||
request.TLSClientConfig(c.tls)
|
||||
}
|
||||
if len(c.token) > 0 {
|
||||
request.Header["Authorization"] = "Bearer " + c.token
|
||||
}
|
||||
request.Query(queryContent)
|
||||
return request
|
||||
}
|
||||
|
||||
// GenericObject generic object
|
||||
type GenericObject struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ListMeta `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (c *clientImpl) watch(url string, labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
watchCh := make(chan interface{}, 10)
|
||||
errCh := make(chan error, 10)
|
||||
|
||||
// get version
|
||||
body, err := c.do(c.request(url, ""))
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to do version request: GET %q : %v", url, err)
|
||||
}
|
||||
|
||||
var generic GenericObject
|
||||
if err := json.Unmarshal(body, &generic); err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to decode version %v", err)
|
||||
}
|
||||
resourceVersion := generic.ResourceVersion
|
||||
queryParams := map[string]string{"watch": "", "resourceVersion": resourceVersion}
|
||||
queryData, err := makeQueryString(queryParams, labelSelector)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("Unable to construct query args")
|
||||
}
|
||||
request := c.request(url, queryData)
|
||||
req, err := request.MakeRequest()
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
|
||||
}
|
||||
request.Client.Transport = request.Transport
|
||||
|
||||
res, err := request.Client.Do(req)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
finishCh := make(chan bool)
|
||||
defer close(finishCh)
|
||||
defer close(watchCh)
|
||||
defer close(errCh)
|
||||
go func() {
|
||||
defer res.Body.Close()
|
||||
for {
|
||||
var eventList interface{}
|
||||
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
|
||||
if !strings.Contains(err.Error(), "net/http: request canceled") {
|
||||
errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err)
|
||||
}
|
||||
finishCh <- true
|
||||
return
|
||||
}
|
||||
watchCh <- eventList
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-stopCh:
|
||||
go func() {
|
||||
request.Transport.CancelRequest(req)
|
||||
}()
|
||||
<-finishCh
|
||||
return
|
||||
}
|
||||
}()
|
||||
return watchCh, errCh, nil
|
||||
}
|
84
integration/vendor/github.com/containous/traefik/provider/k8s/endpoints.go
generated
vendored
Normal file
84
integration/vendor/github.com/containous/traefik/provider/k8s/endpoints.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
package k8s
|
||||
|
||||
// Endpoints is a collection of endpoints that implement the actual service. Example:
|
||||
// Name: "mysvc",
|
||||
// Subsets: [
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
|
||||
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
|
||||
// },
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.3.3"}],
|
||||
// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
|
||||
// },
|
||||
// ]
|
||||
type Endpoints struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// The set of all endpoints is the union of all subsets.
|
||||
Subsets []EndpointSubset
|
||||
}
|
||||
|
||||
// EndpointSubset is a group of addresses with a common set of ports. The
|
||||
// expanded set of endpoints is the Cartesian product of Addresses x Ports.
|
||||
// For example, given:
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
|
||||
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
|
||||
// }
|
||||
// The resulting set of endpoints can be viewed as:
|
||||
// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ],
|
||||
// b: [ 10.10.1.1:309, 10.10.2.2:309 ]
|
||||
type EndpointSubset struct {
|
||||
Addresses []EndpointAddress
|
||||
NotReadyAddresses []EndpointAddress
|
||||
Ports []EndpointPort
|
||||
}
|
||||
|
||||
// EndpointAddress is a tuple that describes single IP address.
|
||||
type EndpointAddress struct {
|
||||
// The IP of this endpoint.
|
||||
// IPv6 is also accepted but not fully supported on all platforms. Also, certain
|
||||
// kubernetes components, like kube-proxy, are not IPv6 ready.
|
||||
// TODO: This should allow hostname or IP, see #4447.
|
||||
IP string
|
||||
// Optional: Hostname of this endpoint
|
||||
// Meant to be used by DNS servers etc.
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
// Optional: The kubernetes object related to the entry point.
|
||||
TargetRef *ObjectReference
|
||||
}
|
||||
|
||||
// EndpointPort is a tuple that describes a single port.
|
||||
type EndpointPort struct {
|
||||
// The name of this port (corresponds to ServicePort.Name). Optional
|
||||
// if only one port is defined. Must be a DNS_LABEL.
|
||||
Name string
|
||||
|
||||
// The port number.
|
||||
Port int32
|
||||
|
||||
// The IP protocol for this port.
|
||||
Protocol Protocol
|
||||
}
|
||||
|
||||
// ObjectReference contains enough information to let you inspect or modify the referred object.
|
||||
type ObjectReference struct {
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
UID UID `json:"uid,omitempty"`
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
ResourceVersion string `json:"resourceVersion,omitempty"`
|
||||
|
||||
// Optional. If referring to a piece of an object instead of an entire object, this string
|
||||
// should contain information to identify the sub-object. For example, if the object
|
||||
// reference is to a container within a pod, this would take on a value like:
|
||||
// "spec.containers{name}" (where "name" refers to the name of the container that triggered
|
||||
// the event) or if no container name is specified "spec.containers[2]" (container with
|
||||
// index 2 in this pod). This syntax is chosen only to have some well-defined way of
|
||||
// referencing a part of an object.
|
||||
// TODO: this design is not final and this field is subject to change in the future.
|
||||
FieldPath string `json:"fieldPath,omitempty"`
|
||||
}
|
151
integration/vendor/github.com/containous/traefik/provider/k8s/ingress.go
generated
vendored
Normal file
151
integration/vendor/github.com/containous/traefik/provider/k8s/ingress.go
generated
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
package k8s
|
||||
|
||||
// Ingress is a collection of rules that allow inbound connections to reach the
|
||||
// endpoints defined by a backend. An Ingress can be configured to give services
|
||||
// externally-reachable urls, load balance traffic, terminate SSL, offer name
|
||||
// based virtual hosting etc.
|
||||
type Ingress struct {
|
||||
TypeMeta `json:",inline"`
|
||||
// Standard object's metadata.
|
||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
|
||||
ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec is the desired state of the Ingress.
|
||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
|
||||
Spec IngressSpec `json:"spec,omitempty"`
|
||||
|
||||
// Status is the current state of the Ingress.
|
||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
|
||||
Status IngressStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// IngressList is a collection of Ingress.
|
||||
type IngressList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
// Standard object's metadata.
|
||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
|
||||
ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Items is the list of Ingress.
|
||||
Items []Ingress `json:"items"`
|
||||
}
|
||||
|
||||
// IngressSpec describes the Ingress the user wishes to exist.
|
||||
type IngressSpec struct {
|
||||
// A default backend capable of servicing requests that don't match any
|
||||
// rule. At least one of 'backend' or 'rules' must be specified. This field
|
||||
// is optional to allow the loadbalancer controller or defaulting logic to
|
||||
// specify a global default.
|
||||
Backend *IngressBackend `json:"backend,omitempty"`
|
||||
|
||||
// TLS configuration. Currently the Ingress only supports a single TLS
|
||||
// port, 443. If multiple members of this list specify different hosts, they
|
||||
// will be multiplexed on the same port according to the hostname specified
|
||||
// through the SNI TLS extension, if the ingress controller fulfilling the
|
||||
// ingress supports SNI.
|
||||
TLS []IngressTLS `json:"tls,omitempty"`
|
||||
|
||||
// A list of host rules used to configure the Ingress. If unspecified, or
|
||||
// no rule matches, all traffic is sent to the default backend.
|
||||
Rules []IngressRule `json:"rules,omitempty"`
|
||||
// TODO: Add the ability to specify load-balancer IP through claims
|
||||
}
|
||||
|
||||
// IngressTLS describes the transport layer security associated with an Ingress.
|
||||
type IngressTLS struct {
|
||||
// Hosts are a list of hosts included in the TLS certificate. The values in
|
||||
// this list must match the name/s used in the tlsSecret. Defaults to the
|
||||
// wildcard host setting for the loadbalancer controller fulfilling this
|
||||
// Ingress, if left unspecified.
|
||||
Hosts []string `json:"hosts,omitempty"`
|
||||
// SecretName is the name of the secret used to terminate SSL traffic on 443.
|
||||
// Field is left optional to allow SSL routing based on SNI hostname alone.
|
||||
// If the SNI host in a listener conflicts with the "Host" header field used
|
||||
// by an IngressRule, the SNI host is used for termination and value of the
|
||||
// Host header is used for routing.
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
// TODO: Consider specifying different modes of termination, protocols etc.
|
||||
}
|
||||
|
||||
// IngressStatus describe the current state of the Ingress.
|
||||
type IngressStatus struct {
|
||||
// LoadBalancer contains the current status of the load-balancer.
|
||||
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
|
||||
}
|
||||
|
||||
// IngressRule represents the rules mapping the paths under a specified host to
|
||||
// the related backend services. Incoming requests are first evaluated for a host
|
||||
// match, then routed to the backend associated with the matching IngressRuleValue.
|
||||
type IngressRule struct {
|
||||
// Host is the fully qualified domain name of a network host, as defined
|
||||
// by RFC 3986. Note the following deviations from the "host" part of the
|
||||
// URI as defined in the RFC:
|
||||
// 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the
|
||||
// IP in the Spec of the parent Ingress.
|
||||
// 2. The `:` delimiter is not respected because ports are not allowed.
|
||||
// Currently the port of an Ingress is implicitly :80 for http and
|
||||
// :443 for https.
|
||||
// Both these may change in the future.
|
||||
// Incoming requests are matched against the host before the IngressRuleValue.
|
||||
// If the host is unspecified, the Ingress routes all traffic based on the
|
||||
// specified IngressRuleValue.
|
||||
Host string `json:"host,omitempty"`
|
||||
// IngressRuleValue represents a rule to route requests for this IngressRule.
|
||||
// If unspecified, the rule defaults to a http catch-all. Whether that sends
|
||||
// just traffic matching the host to the default backend or all traffic to the
|
||||
// default backend, is left to the controller fulfilling the Ingress. Http is
|
||||
// currently the only supported IngressRuleValue.
|
||||
IngressRuleValue `json:",inline,omitempty"`
|
||||
}
|
||||
|
||||
// IngressRuleValue represents a rule to apply against incoming requests. If the
|
||||
// rule is satisfied, the request is routed to the specified backend. Currently
|
||||
// mixing different types of rules in a single Ingress is disallowed, so exactly
|
||||
// one of the following must be set.
|
||||
type IngressRuleValue struct {
|
||||
//TODO:
|
||||
// 1. Consider renaming this resource and the associated rules so they
|
||||
// aren't tied to Ingress. They can be used to route intra-cluster traffic.
|
||||
// 2. Consider adding fields for ingress-type specific global options
|
||||
// usable by a loadbalancer, like http keep-alive.
|
||||
|
||||
HTTP *HTTPIngressRuleValue `json:"http,omitempty"`
|
||||
}
|
||||
|
||||
// HTTPIngressRuleValue is a list of http selectors pointing to backends.
|
||||
// In the example: http://<host>/<path>?<searchpart> -> backend where
|
||||
// where parts of the url correspond to RFC 3986, this resource will be used
|
||||
// to match against everything after the last '/' and before the first '?'
|
||||
// or '#'.
|
||||
type HTTPIngressRuleValue struct {
|
||||
// A collection of paths that map requests to backends.
|
||||
Paths []HTTPIngressPath `json:"paths"`
|
||||
// TODO: Consider adding fields for ingress-type specific global
|
||||
// options usable by a loadbalancer, like http keep-alive.
|
||||
}
|
||||
|
||||
// HTTPIngressPath associates a path regex with a backend. Incoming urls matching
|
||||
// the path are forwarded to the backend.
|
||||
type HTTPIngressPath struct {
|
||||
// Path is a extended POSIX regex as defined by IEEE Std 1003.1,
|
||||
// (i.e this follows the egrep/unix syntax, not the perl syntax)
|
||||
// matched against the path of an incoming request. Currently it can
|
||||
// contain characters disallowed from the conventional "path"
|
||||
// part of a URL as defined by RFC 3986. Paths must begin with
|
||||
// a '/'. If unspecified, the path defaults to a catch all sending
|
||||
// traffic to the backend.
|
||||
Path string `json:"path,omitempty"`
|
||||
|
||||
// Backend defines the referenced service endpoint to which the traffic
|
||||
// will be forwarded to.
|
||||
Backend IngressBackend `json:"backend"`
|
||||
}
|
||||
|
||||
// IngressBackend describes all endpoints for a given service and port.
|
||||
type IngressBackend struct {
|
||||
// Specifies the name of the referenced service.
|
||||
ServiceName string `json:"serviceName"`
|
||||
|
||||
// Specifies the port of the referenced service.
|
||||
ServicePort IntOrString `json:"servicePort"`
|
||||
}
|
326
integration/vendor/github.com/containous/traefik/provider/k8s/service.go
generated
vendored
Normal file
326
integration/vendor/github.com/containous/traefik/provider/k8s/service.go
generated
vendored
Normal file
|
@ -0,0 +1,326 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TypeMeta describes an individual object in an API response or request
|
||||
// with strings representing the type of the object and its API schema version.
|
||||
// Structures that are versioned or persisted should inline TypeMeta.
|
||||
type TypeMeta struct {
|
||||
// Kind is a string value representing the REST resource this object represents.
|
||||
// Servers may infer this from the endpoint the client submits requests to.
|
||||
// Cannot be updated.
|
||||
// In CamelCase.
|
||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
|
||||
Kind string `json:"kind,omitempty"`
|
||||
|
||||
// APIVersion defines the versioned schema of this representation of an object.
|
||||
// Servers should convert recognized schemas to the latest internal value, and
|
||||
// may reject unrecognized values.
|
||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
}
|
||||
|
||||
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
|
||||
// users must create.
|
||||
type ObjectMeta struct {
|
||||
// Name is unique within a namespace. Name is required when creating resources, although
|
||||
// some resources may allow a client to request the generation of an appropriate name
|
||||
// automatically. Name is primarily intended for creation idempotence and configuration
|
||||
// definition.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// GenerateName indicates that the name should be made unique by the server prior to persisting
|
||||
// it. A non-empty value for the field indicates the name will be made unique (and the name
|
||||
// returned to the client will be different than the name passed). The value of this field will
|
||||
// be combined with a unique suffix on the server if the Name field has not been provided.
|
||||
// The provided value must be valid within the rules for Name, and may be truncated by the length
|
||||
// of the suffix required to make the value unique on the server.
|
||||
//
|
||||
// If this field is specified, and Name is not present, the server will NOT return a 409 if the
|
||||
// generated name exists - instead, it will either return 201 Created or 500 with Reason
|
||||
// ServerTimeout indicating a unique name could not be found in the time allotted, and the client
|
||||
// should retry (optionally after the time indicated in the Retry-After header).
|
||||
GenerateName string `json:"generateName,omitempty"`
|
||||
|
||||
// Namespace defines the space within which name must be unique. An empty namespace is
|
||||
// equivalent to the "default" namespace, but "default" is the canonical representation.
|
||||
// Not all objects are required to be scoped to a namespace - the value of this field for
|
||||
// those objects will be empty.
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
// SelfLink is a URL representing this object.
|
||||
SelfLink string `json:"selfLink,omitempty"`
|
||||
|
||||
// UID is the unique in time and space value for this object. It is typically generated by
|
||||
// the server on successful creation of a resource and is not allowed to change on PUT
|
||||
// operations.
|
||||
UID UID `json:"uid,omitempty"`
|
||||
|
||||
// An opaque value that represents the version of this resource. May be used for optimistic
|
||||
// concurrency, change detection, and the watch operation on a resource or set of resources.
|
||||
// Clients must treat these values as opaque and values may only be valid for a particular
|
||||
// resource or set of resources. Only servers will generate resource versions.
|
||||
ResourceVersion string `json:"resourceVersion,omitempty"`
|
||||
|
||||
// A sequence number representing a specific generation of the desired state.
|
||||
// Populated by the system. Read-only.
|
||||
Generation int64 `json:"generation,omitempty"`
|
||||
|
||||
// CreationTimestamp is a timestamp representing the server time when this object was
|
||||
// created. It is not guaranteed to be set in happens-before order across separate operations.
|
||||
// Clients may not set this value. It is represented in RFC3339 form and is in UTC.
|
||||
CreationTimestamp Time `json:"creationTimestamp,omitempty"`
|
||||
|
||||
// DeletionTimestamp is the time after which this resource will be deleted. This
|
||||
// field is set by the server when a graceful deletion is requested by the user, and is not
|
||||
// directly settable by a client. The resource will be deleted (no longer visible from
|
||||
// resource lists, and not reachable by name) after the time in this field. Once set, this
|
||||
// value may not be unset or be set further into the future, although it may be shortened
|
||||
// or the resource may be deleted prior to this time. For example, a user may request that
|
||||
// a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination
|
||||
// signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet
|
||||
// will send a hard termination signal to the container.
|
||||
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty"`
|
||||
|
||||
// DeletionGracePeriodSeconds records the graceful deletion value set when graceful deletion
|
||||
// was requested. Represents the most recent grace period, and may only be shortened once set.
|
||||
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty"`
|
||||
|
||||
// Labels are key value pairs that may be used to scope and select individual resources.
|
||||
// Label keys are of the form:
|
||||
// label-key ::= prefixed-name | name
|
||||
// prefixed-name ::= prefix '/' name
|
||||
// prefix ::= DNS_SUBDOMAIN
|
||||
// name ::= DNS_LABEL
|
||||
// The prefix is optional. If the prefix is not specified, the key is assumed to be private
|
||||
// to the user. Other system components that wish to use labels must specify a prefix. The
|
||||
// "kubernetes.io/" prefix is reserved for use by kubernetes components.
|
||||
// TODO: replace map[string]string with labels.LabelSet type
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
// Annotations are unstructured key value data stored with a resource that may be set by
|
||||
// external tooling. They are not queryable and should be preserved when modifying
|
||||
// objects. Annotation keys have the same formatting restrictions as Label keys. See the
|
||||
// comments on Labels for details.
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// UID is a type that holds unique ID values, including UUIDs. Because we
|
||||
// don't ONLY use UUIDs, this is an alias to string. Being a type captures
|
||||
// intent and helps make sure that UIDs and names do not get conflated.
|
||||
type UID string
|
||||
|
||||
// Time is a wrapper around time.Time which supports correct
|
||||
// marshaling to YAML and JSON. Wrappers are provided for many
|
||||
// of the factory methods that the time package offers.
|
||||
//
|
||||
// +protobuf.options.marshal=false
|
||||
// +protobuf.as=Timestamp
|
||||
type Time struct {
|
||||
time.Time `protobuf:"-"`
|
||||
}
|
||||
|
||||
// Service is a named abstraction of software service (for example, mysql) consisting of local port
|
||||
// (for example 3306) that the proxy listens on, and the selector that determines which pods
|
||||
// will answer requests sent through the proxy.
|
||||
type Service struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec defines the behavior of a service.
|
||||
Spec ServiceSpec `json:"spec,omitempty"`
|
||||
|
||||
// Status represents the current status of a service.
|
||||
Status ServiceStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// ServiceSpec describes the attributes that a user creates on a service
|
||||
type ServiceSpec struct {
|
||||
// Type determines how the service will be exposed. Valid options: ClusterIP, NodePort, LoadBalancer
|
||||
Type ServiceType `json:"type,omitempty"`
|
||||
|
||||
// Required: The list of ports that are exposed by this service.
|
||||
Ports []ServicePort `json:"ports"`
|
||||
|
||||
// This service will route traffic to pods having labels matching this selector. If empty or not present,
|
||||
// the service is assumed to have endpoints set by an external process and Kubernetes will not modify
|
||||
// those endpoints.
|
||||
Selector map[string]string `json:"selector"`
|
||||
|
||||
// ClusterIP is usually assigned by the master. If specified by the user
|
||||
// we will try to respect it or else fail the request. This field can
|
||||
// not be changed by updates.
|
||||
// Valid values are None, empty string (""), or a valid IP address
|
||||
// None can be specified for headless services when proxying is not required
|
||||
ClusterIP string `json:"clusterIP,omitempty"`
|
||||
|
||||
// ExternalIPs are used by external load balancers, or can be set by
|
||||
// users to handle external traffic that arrives at a node.
|
||||
ExternalIPs []string `json:"externalIPs,omitempty"`
|
||||
|
||||
// Only applies to Service Type: LoadBalancer
|
||||
// LoadBalancer will get created with the IP specified in this field.
|
||||
// This feature depends on whether the underlying cloud-provider supports specifying
|
||||
// the loadBalancerIP when a load balancer is created.
|
||||
// This field will be ignored if the cloud-provider does not support the feature.
|
||||
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
|
||||
|
||||
// Required: Supports "ClientIP" and "None". Used to maintain session affinity.
|
||||
SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"`
|
||||
}
|
||||
|
||||
// ServicePort service port
|
||||
type ServicePort struct {
|
||||
// Optional if only one ServicePort is defined on this service: The
|
||||
// name of this port within the service. This must be a DNS_LABEL.
|
||||
// All ports within a ServiceSpec must have unique names. This maps to
|
||||
// the 'Name' field in EndpointPort objects.
|
||||
Name string `json:"name"`
|
||||
|
||||
// The IP protocol for this port. Supports "TCP" and "UDP".
|
||||
Protocol Protocol `json:"protocol"`
|
||||
|
||||
// The port that will be exposed on the service.
|
||||
Port int `json:"port"`
|
||||
|
||||
// Optional: The target port on pods selected by this service. If this
|
||||
// is a string, it will be looked up as a named port in the target
|
||||
// Pod's container ports. If this is not specified, the value
|
||||
// of the 'port' field is used (an identity map).
|
||||
// This field is ignored for services with clusterIP=None, and should be
|
||||
// omitted or set equal to the 'port' field.
|
||||
TargetPort IntOrString `json:"targetPort"`
|
||||
|
||||
// The port on each node on which this service is exposed.
|
||||
// Default is to auto-allocate a port if the ServiceType of this Service requires one.
|
||||
NodePort int `json:"nodePort"`
|
||||
}
|
||||
|
||||
// ServiceStatus represents the current status of a service
|
||||
type ServiceStatus struct {
|
||||
// LoadBalancer contains the current status of the load-balancer,
|
||||
// if one is present.
|
||||
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
|
||||
}
|
||||
|
||||
// LoadBalancerStatus represents the status of a load-balancer
|
||||
type LoadBalancerStatus struct {
|
||||
// Ingress is a list containing ingress points for the load-balancer;
|
||||
// traffic intended for the service should be sent to these ingress points.
|
||||
Ingress []LoadBalancerIngress `json:"ingress,omitempty"`
|
||||
}
|
||||
|
||||
// LoadBalancerIngress represents the status of a load-balancer ingress point:
|
||||
// traffic intended for the service should be sent to an ingress point.
|
||||
type LoadBalancerIngress struct {
|
||||
// IP is set for load-balancer ingress points that are IP based
|
||||
// (typically GCE or OpenStack load-balancers)
|
||||
IP string `json:"ip,omitempty"`
|
||||
|
||||
// Hostname is set for load-balancer ingress points that are DNS based
|
||||
// (typically AWS load-balancers)
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
}
|
||||
|
||||
// ServiceAffinity Session Affinity Type string
|
||||
type ServiceAffinity string
|
||||
|
||||
// ServiceType Service Type string describes ingress methods for a service
|
||||
type ServiceType string
|
||||
|
||||
// Protocol defines network protocols supported for things like container ports.
|
||||
type Protocol string
|
||||
|
||||
// IntOrString is a type that can hold an int32 or a string. When used in
|
||||
// JSON or YAML marshalling and unmarshalling, it produces or consumes the
|
||||
// inner type. This allows you to have, for example, a JSON field that can
|
||||
// accept a name or number.
|
||||
// TODO: Rename to Int32OrString
|
||||
//
|
||||
// +protobuf=true
|
||||
// +protobuf.options.(gogoproto.goproto_stringer)=false
|
||||
type IntOrString struct {
|
||||
Type Type
|
||||
IntVal int32
|
||||
StrVal string
|
||||
}
|
||||
|
||||
// FromInt creates an IntOrString object with an int32 value. It is
|
||||
// your responsibility not to call this method with a value greater
|
||||
// than int32.
|
||||
// TODO: convert to (val int32)
|
||||
func FromInt(val int) IntOrString {
|
||||
return IntOrString{Type: Int, IntVal: int32(val)}
|
||||
}
|
||||
|
||||
// FromString creates an IntOrString object with a string value.
|
||||
func FromString(val string) IntOrString {
|
||||
return IntOrString{Type: String, StrVal: val}
|
||||
}
|
||||
|
||||
// String returns the string value, or the Itoa of the int value.
|
||||
func (intstr *IntOrString) String() string {
|
||||
if intstr.Type == String {
|
||||
return intstr.StrVal
|
||||
}
|
||||
return strconv.Itoa(intstr.IntValue())
|
||||
}
|
||||
|
||||
// IntValue returns the IntVal if type Int, or if
|
||||
// it is a String, will attempt a conversion to int.
|
||||
func (intstr *IntOrString) IntValue() int {
|
||||
if intstr.Type == String {
|
||||
i, _ := strconv.Atoi(intstr.StrVal)
|
||||
return i
|
||||
}
|
||||
return int(intstr.IntVal)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaller interface.
|
||||
func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
|
||||
if value[0] == '"' {
|
||||
intstr.Type = String
|
||||
return json.Unmarshal(value, &intstr.StrVal)
|
||||
}
|
||||
intstr.Type = Int
|
||||
return json.Unmarshal(value, &intstr.IntVal)
|
||||
}
|
||||
|
||||
// Type represents the stored type of IntOrString.
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// Int int
|
||||
Int Type = iota // The IntOrString holds an int.
|
||||
//String string
|
||||
String // The IntOrString holds a string.
|
||||
)
|
||||
|
||||
// ServiceList holds a list of services.
|
||||
type ServiceList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []Service `json:"items"`
|
||||
}
|
||||
|
||||
// ListMeta describes metadata that synthetic resources must have, including lists and
|
||||
// various status objects. A resource may have only one of {ObjectMeta, ListMeta}.
|
||||
type ListMeta struct {
|
||||
// SelfLink is a URL representing this object.
|
||||
// Populated by the system.
|
||||
// Read-only.
|
||||
SelfLink string `json:"selfLink,omitempty"`
|
||||
|
||||
// String that identifies the server's internal version of this object that
|
||||
// can be used by clients to determine when objects have changed.
|
||||
// Value must be treated as opaque by clients and passed unmodified back to the server.
|
||||
// Populated by the system.
|
||||
// Read-only.
|
||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#concurrency-control-and-consistency
|
||||
ResourceVersion string `json:"resourceVersion,omitempty"`
|
||||
}
|
328
integration/vendor/github.com/containous/traefik/provider/kubernetes.go
generated
vendored
Normal file
328
integration/vendor/github.com/containous/traefik/provider/kubernetes.go
generated
vendored
Normal file
|
@ -0,0 +1,328 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/k8s"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
)
|
||||
|
||||
const (
|
||||
serviceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
defaultKubeEndpoint = "http://127.0.0.1:8080"
|
||||
)
|
||||
|
||||
// Namespaces holds kubernetes namespaces
|
||||
type Namespaces []string
|
||||
|
||||
//Set adds strings elem into the the parser
|
||||
//it splits str on , and ;
|
||||
func (ns *Namespaces) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
*ns = append(*ns, slice...)
|
||||
return nil
|
||||
}
|
||||
|
||||
//Get []string
|
||||
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
|
||||
|
||||
//String return slice in a string
|
||||
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
|
||||
|
||||
//SetValue sets []string into the parser
|
||||
func (ns *Namespaces) SetValue(val interface{}) {
|
||||
*ns = Namespaces(val.(Namespaces))
|
||||
}
|
||||
|
||||
var _ Provider = (*Kubernetes)(nil)
|
||||
|
||||
// Kubernetes holds configurations of the Kubernetes provider.
|
||||
type Kubernetes struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string `description:"Kubernetes server endpoint"`
|
||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
|
||||
Namespaces Namespaces `description:"Kubernetes namespaces"`
|
||||
LabelSelector string `description:"Kubernetes api label selector to use"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||
var token string
|
||||
tokenBytes, err := ioutil.ReadFile(serviceAccountToken)
|
||||
if err == nil {
|
||||
token = string(tokenBytes)
|
||||
log.Debugf("Kubernetes token: %s", token)
|
||||
} else {
|
||||
log.Errorf("Kubernetes load token error: %s", err)
|
||||
}
|
||||
caCert, err := ioutil.ReadFile(serviceAccountCACert)
|
||||
if err == nil {
|
||||
log.Debugf("Kubernetes CA cert: %s", serviceAccountCACert)
|
||||
} else {
|
||||
log.Errorf("Kubernetes load token error: %s", err)
|
||||
}
|
||||
kubernetesHost := os.Getenv("KUBERNETES_SERVICE_HOST")
|
||||
kubernetesPort := os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")
|
||||
// Prioritize user provided kubernetes endpoint since kube container runtime will almost always have it
|
||||
if provider.Endpoint == "" && len(kubernetesPort) > 0 && len(kubernetesHost) > 0 {
|
||||
log.Debugf("Using environment provided kubernetes endpoint")
|
||||
provider.Endpoint = "https://" + kubernetesHost + ":" + kubernetesPort
|
||||
}
|
||||
if provider.Endpoint == "" {
|
||||
log.Debugf("Using default kubernetes api endpoint")
|
||||
provider.Endpoint = defaultKubeEndpoint
|
||||
}
|
||||
log.Debugf("Kubernetes endpoint: %s", provider.Endpoint)
|
||||
return k8s.NewClient(provider.Endpoint, caCert, token)
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
k8sClient, err := provider.createClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
|
||||
pool.Go(func(stop chan bool) {
|
||||
operation := func() error {
|
||||
for {
|
||||
stopWatch := make(chan bool, 5)
|
||||
defer close(stopWatch)
|
||||
log.Debugf("Using label selector: '%s'", provider.LabelSelector)
|
||||
eventsChan, errEventsChan, err := k8sClient.WatchAll(provider.LabelSelector, stopWatch)
|
||||
if err != nil {
|
||||
log.Errorf("Error watching kubernetes events: %v", err)
|
||||
timer := time.NewTimer(1 * time.Second)
|
||||
select {
|
||||
case <-timer.C:
|
||||
return err
|
||||
case <-stop:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
stopWatch <- true
|
||||
return nil
|
||||
case err, _ := <-errEventsChan:
|
||||
stopWatch <- true
|
||||
return err
|
||||
case event := <-eventsChan:
|
||||
log.Debugf("Received event from kubernetes %+v", event)
|
||||
templateObjects, err := provider.loadIngresses(k8sClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
|
||||
log.Debugf("Skipping event from kubernetes %+v", event)
|
||||
} else {
|
||||
provider.lastConfiguration.Set(templateObjects)
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "kubernetes",
|
||||
Configuration: provider.loadConfig(*templateObjects),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Kubernetes connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to Kubernetes server %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
templateObjects, err := provider.loadIngresses(k8sClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
|
||||
log.Debugf("Skipping configuration from kubernetes %+v", templateObjects)
|
||||
} else {
|
||||
provider.lastConfiguration.Set(templateObjects)
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "kubernetes",
|
||||
Configuration: provider.loadConfig(*templateObjects),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
|
||||
ingresses, err := k8sClient.GetIngresses(provider.LabelSelector, func(ingress k8s.Ingress) bool {
|
||||
if len(provider.Namespaces) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, n := range provider.Namespaces {
|
||||
if ingress.ObjectMeta.Namespace == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Error retrieving ingresses: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
templateObjects := types.Configuration{
|
||||
map[string]*types.Backend{},
|
||||
map[string]*types.Frontend{},
|
||||
}
|
||||
PassHostHeader := provider.getPassHostHeader()
|
||||
for _, i := range ingresses {
|
||||
for _, r := range i.Spec.Rules {
|
||||
for _, pa := range r.HTTP.Paths {
|
||||
if _, exists := templateObjects.Backends[r.Host+pa.Path]; !exists {
|
||||
templateObjects.Backends[r.Host+pa.Path] = &types.Backend{
|
||||
Servers: make(map[string]types.Server),
|
||||
}
|
||||
}
|
||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
|
||||
templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{
|
||||
Backend: r.Host + pa.Path,
|
||||
PassHostHeader: PassHostHeader,
|
||||
Routes: make(map[string]types.Route),
|
||||
Priority: len(pa.Path),
|
||||
}
|
||||
}
|
||||
if len(r.Host) > 0 {
|
||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
|
||||
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
|
||||
Rule: "Host:" + r.Host,
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(pa.Path) > 0 {
|
||||
ruleType := i.Annotations["traefik.frontend.rule.type"]
|
||||
|
||||
switch strings.ToLower(ruleType) {
|
||||
case "pathprefixstrip":
|
||||
ruleType = "PathPrefixStrip"
|
||||
case "pathstrip":
|
||||
ruleType = "PathStrip"
|
||||
case "path":
|
||||
ruleType = "Path"
|
||||
case "pathprefix":
|
||||
ruleType = "PathPrefix"
|
||||
case "":
|
||||
ruleType = "PathPrefix"
|
||||
default:
|
||||
log.Warnf("Unknown RuleType %s for %s/%s, falling back to PathPrefix", ruleType, i.ObjectMeta.Namespace, i.ObjectMeta.Name)
|
||||
ruleType = "PathPrefix"
|
||||
}
|
||||
|
||||
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
|
||||
Rule: ruleType + ":" + pa.Path,
|
||||
}
|
||||
}
|
||||
service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace)
|
||||
if err != nil {
|
||||
log.Warnf("Error retrieving services: %v", err)
|
||||
delete(templateObjects.Frontends, r.Host+pa.Path)
|
||||
log.Warnf("Error retrieving services %s", pa.Backend.ServiceName)
|
||||
continue
|
||||
}
|
||||
protocol := "http"
|
||||
for _, port := range service.Spec.Ports {
|
||||
if equalPorts(port, pa.Backend.ServicePort) {
|
||||
if port.Port == 443 {
|
||||
protocol = "https"
|
||||
}
|
||||
endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace)
|
||||
if err != nil {
|
||||
log.Errorf("Error retrieving endpoints: %v", err)
|
||||
continue
|
||||
}
|
||||
if len(endpoints.Subsets) == 0 {
|
||||
log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
|
||||
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
|
||||
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
|
||||
Weight: 1,
|
||||
}
|
||||
} else {
|
||||
for _, subset := range endpoints.Subsets {
|
||||
for _, address := range subset.Addresses {
|
||||
url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
|
||||
name := url
|
||||
if address.TargetRef != nil && address.TargetRef.Name != "" {
|
||||
name = address.TargetRef.Name
|
||||
}
|
||||
templateObjects.Backends[r.Host+pa.Path].Servers[name] = types.Server{
|
||||
URL: url,
|
||||
Weight: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &templateObjects, nil
|
||||
}
|
||||
|
||||
func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int {
|
||||
if len(endpointPorts) > 0 {
|
||||
//name is optional if there is only one port
|
||||
port := endpointPorts[0]
|
||||
for _, endpointPort := range endpointPorts {
|
||||
if servicePort.Name == endpointPort.Name {
|
||||
port = endpointPort
|
||||
}
|
||||
}
|
||||
return int(port.Port)
|
||||
}
|
||||
return servicePort.Port
|
||||
}
|
||||
|
||||
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
|
||||
if servicePort.Port == ingressPort.IntValue() {
|
||||
return true
|
||||
}
|
||||
if servicePort.Name != "" && servicePort.Name == ingressPort.String() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) getPassHostHeader() bool {
|
||||
if provider.DisablePassHostHeaders {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) loadConfig(templateObjects types.Configuration) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{}
|
||||
configuration, err := provider.getConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return configuration
|
||||
}
|
218
integration/vendor/github.com/containous/traefik/provider/kv.go
generated
vendored
Normal file
218
integration/vendor/github.com/containous/traefik/provider/kv.go
generated
vendored
Normal file
|
@ -0,0 +1,218 @@
|
|||
// Package provider holds the different provider implementation.
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv"
|
||||
"github.com/docker/libkv/store"
|
||||
)
|
||||
|
||||
// Kv holds common configurations of key-value providers.
|
||||
type Kv struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string `description:"Comma sepparated server endpoints"`
|
||||
Prefix string `description:"Prefix used for KV store"`
|
||||
TLS *ClientTLS `description:"Enable TLS support"`
|
||||
storeType store.Backend
|
||||
kvclient store.Store
|
||||
}
|
||||
|
||||
func (provider *Kv) createStore() (store.Store, error) {
|
||||
storeConfig := &store.Config{
|
||||
ConnectionTimeout: 30 * time.Second,
|
||||
Bucket: "traefik",
|
||||
}
|
||||
|
||||
if provider.TLS != nil {
|
||||
var err error
|
||||
storeConfig.TLS, err = provider.TLS.CreateTLSConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return libkv.NewStore(
|
||||
provider.storeType,
|
||||
strings.Split(provider.Endpoint, ","),
|
||||
storeConfig,
|
||||
)
|
||||
}
|
||||
|
||||
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
|
||||
operation := func() error {
|
||||
events, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to KV WatchTree: %v", err)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return nil
|
||||
case _, ok := <-events:
|
||||
if !ok {
|
||||
return errors.New("watchtree channel closed")
|
||||
}
|
||||
configuration := provider.loadConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: string(provider.storeType),
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("KV connection error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
operation := func() error {
|
||||
if _, err := provider.kvclient.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
|
||||
return fmt.Errorf("Failed to test KV store connection: %v", err)
|
||||
}
|
||||
if provider.Watch {
|
||||
pool.Go(func(stop chan bool) {
|
||||
err := provider.watchKv(configurationChan, provider.Prefix, stop)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot watch KV store: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
configuration := provider.loadConfig()
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: string(provider.storeType),
|
||||
Configuration: configuration,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("KV connection error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Kv) loadConfig() *types.Configuration {
|
||||
templateObjects := struct {
|
||||
Prefix string
|
||||
}{
|
||||
// Allow `/traefik/alias` to superesede `provider.Prefix`
|
||||
strings.TrimSuffix(provider.get(provider.Prefix, provider.Prefix+"/alias"), "/"),
|
||||
}
|
||||
|
||||
var KvFuncMap = template.FuncMap{
|
||||
"List": provider.list,
|
||||
"ListServers": provider.listServers,
|
||||
"Get": provider.get,
|
||||
"SplitGet": provider.splitGet,
|
||||
"Last": provider.last,
|
||||
}
|
||||
|
||||
configuration, err := provider.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 == false {
|
||||
delete(configuration.Frontends, key)
|
||||
}
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
func (provider *Kv) list(keys ...string) []string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keysPairs, err := provider.kvclient.List(joinedKeys)
|
||||
if err != nil {
|
||||
log.Debugf("Cannot get keys %s %s ", joinedKeys, err)
|
||||
return nil
|
||||
}
|
||||
directoryKeys := make(map[string]string)
|
||||
for _, key := range keysPairs {
|
||||
directory := strings.Split(strings.TrimPrefix(key.Key, joinedKeys), "/")[0]
|
||||
directoryKeys[directory] = joinedKeys + directory
|
||||
}
|
||||
return fun.Values(directoryKeys).([]string)
|
||||
}
|
||||
|
||||
func (provider *Kv) listServers(backend string) []string {
|
||||
serverNames := provider.list(backend, "/servers/")
|
||||
return fun.Filter(func(serverName string) bool {
|
||||
return provider.checkConstraints(serverName, "/tags")
|
||||
}, serverNames).([]string)
|
||||
}
|
||||
|
||||
func (provider *Kv) get(defaultValue string, keys ...string) string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := provider.kvclient.Get(strings.TrimPrefix(joinedKeys, "/"))
|
||||
if err != nil {
|
||||
log.Debugf("Cannot get key %s %s, setting default %s", joinedKeys, err, defaultValue)
|
||||
return defaultValue
|
||||
} else if keyPair == nil {
|
||||
log.Debugf("Cannot get key %s, setting default %s", joinedKeys, defaultValue)
|
||||
return defaultValue
|
||||
}
|
||||
return string(keyPair.Value)
|
||||
}
|
||||
|
||||
func (provider *Kv) splitGet(keys ...string) []string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := provider.kvclient.Get(joinedKeys)
|
||||
if err != nil {
|
||||
log.Debugf("Cannot get key %s %s, setting default empty", joinedKeys, err)
|
||||
return []string{}
|
||||
} else if keyPair == nil {
|
||||
log.Debugf("Cannot get key %s, setting default %empty", joinedKeys)
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(string(keyPair.Value), ",")
|
||||
}
|
||||
|
||||
func (provider *Kv) last(key string) string {
|
||||
splittedKey := strings.Split(key, "/")
|
||||
return splittedKey[len(splittedKey)-1]
|
||||
}
|
||||
|
||||
func (provider *Kv) checkConstraints(keys ...string) bool {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := provider.kvclient.Get(joinedKeys)
|
||||
|
||||
value := ""
|
||||
if err == nil && keyPair != nil && keyPair.Value != nil {
|
||||
value = string(keyPair.Value)
|
||||
}
|
||||
|
||||
constraintTags := strings.Split(value, ",")
|
||||
ok, failingConstraint := provider.MatchConstraints(constraintTags)
|
||||
if ok == false {
|
||||
if failingConstraint != nil {
|
||||
log.Debugf("Constraint %v not matching with following tags: %v", failingConstraint.String(), value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
494
integration/vendor/github.com/containous/traefik/provider/marathon.go
generated
vendored
Normal file
494
integration/vendor/github.com/containous/traefik/provider/marathon.go
generated
vendored
Normal file
|
@ -0,0 +1,494 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"math"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/gambol99/go-marathon"
|
||||
)
|
||||
|
||||
var _ Provider = (*Marathon)(nil)
|
||||
|
||||
// Marathon holds configuration of the Marathon provider.
|
||||
type Marathon struct {
|
||||
BaseProvider
|
||||
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
|
||||
Domain string `description:"Default domain used"`
|
||||
ExposedByDefault bool `description:"Expose Marathon apps by default"`
|
||||
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
|
||||
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"`
|
||||
MarathonLBCompatibility bool `description:"Add compatibility with marathon-lb labels"`
|
||||
TLS *ClientTLS `description:"Enable Docker TLS support"`
|
||||
DialerTimeout time.Duration `description:"Set a non-default connection timeout for Marathon"`
|
||||
Basic *MarathonBasic
|
||||
marathonClient marathon.Marathon
|
||||
}
|
||||
|
||||
// MarathonBasic holds basic authentication specific configurations
|
||||
type MarathonBasic struct {
|
||||
HTTPBasicAuthUser string
|
||||
HTTPBasicPassword string
|
||||
}
|
||||
|
||||
type lightMarathonClient interface {
|
||||
AllTasks(v url.Values) (*marathon.Tasks, error)
|
||||
Applications(url.Values) (*marathon.Applications, error)
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
operation := func() error {
|
||||
config := marathon.NewDefaultConfig()
|
||||
config.URL = provider.Endpoint
|
||||
config.EventsTransport = marathon.EventsTransportSSE
|
||||
if provider.Basic != nil {
|
||||
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
|
||||
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
|
||||
}
|
||||
if len(provider.DCOSToken) > 0 {
|
||||
config.DCOSToken = provider.DCOSToken
|
||||
}
|
||||
TLSConfig, err := provider.TLS.CreateTLSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.HTTPClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: TLSConfig,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: time.Second * provider.DialerTimeout,
|
||||
}).DialContext,
|
||||
},
|
||||
}
|
||||
client, err := marathon.NewClient(config)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return err
|
||||
}
|
||||
provider.marathonClient = client
|
||||
update := make(marathon.EventsChannel, 5)
|
||||
if provider.Watch {
|
||||
if err := client.AddEventsListener(update, marathon.EventIDApplications); err != nil {
|
||||
log.Errorf("Failed to register for events, %s", err)
|
||||
return err
|
||||
}
|
||||
pool.Go(func(stop chan bool) {
|
||||
defer close(update)
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case event := <-update:
|
||||
log.Debug("Marathon event receveived", event)
|
||||
configuration := provider.loadMarathonConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "marathon",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
configuration := provider.loadMarathonConfig()
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "marathon",
|
||||
Configuration: configuration,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Marathon connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to Marathon server %+v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
||||
var MarathonFuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getFrontendBackend": provider.getFrontendBackend,
|
||||
"replace": replace,
|
||||
"hasCircuitBreakerLabels": provider.hasCircuitBreakerLabels,
|
||||
"hasLoadBalancerLabels": provider.hasLoadBalancerLabels,
|
||||
"hasMaxConnLabels": provider.hasMaxConnLabels,
|
||||
"getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc,
|
||||
"getMaxConnAmount": provider.getMaxConnAmount,
|
||||
"getLoadBalancerMethod": provider.getLoadBalancerMethod,
|
||||
"getCircuitBreakerExpression": provider.getCircuitBreakerExpression,
|
||||
"getSticky": provider.getSticky,
|
||||
}
|
||||
|
||||
applications, err := provider.marathonClient.Applications(nil)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
tasks, err := provider.marathonClient.AllTasks(&marathon.AllTasksOpts{Status: "running"})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
//filter tasks
|
||||
filteredTasks := fun.Filter(func(task marathon.Task) bool {
|
||||
return provider.taskFilter(task, applications, provider.ExposedByDefault)
|
||||
}, tasks.Tasks).([]marathon.Task)
|
||||
|
||||
//filter apps
|
||||
filteredApps := fun.Filter(func(app marathon.Application) bool {
|
||||
return provider.applicationFilter(app, filteredTasks)
|
||||
}, applications.Apps).([]marathon.Application)
|
||||
|
||||
templateObjects := struct {
|
||||
Applications []marathon.Application
|
||||
Tasks []marathon.Task
|
||||
Domain string
|
||||
}{
|
||||
filteredApps,
|
||||
filteredTasks,
|
||||
provider.Domain,
|
||||
}
|
||||
|
||||
configuration, err := provider.getConfiguration("templates/marathon.tmpl", MarathonFuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon.Applications, exposedByDefaultFlag bool) bool {
|
||||
if len(task.Ports) == 0 {
|
||||
log.Debug("Filtering marathon task without port %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
application, err := getApplication(task, applications.Apps)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
label, _ := provider.getLabel(application, "traefik.tags")
|
||||
constraintTags := strings.Split(label, ",")
|
||||
if provider.MarathonLBCompatibility {
|
||||
if label, err := provider.getLabel(application, "HAPROXY_GROUP"); err == nil {
|
||||
constraintTags = append(constraintTags, label)
|
||||
}
|
||||
}
|
||||
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
|
||||
if failingConstraint != nil {
|
||||
log.Debugf("Application %v pruned by '%v' constraint", application.ID, failingConstraint.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if !isApplicationEnabled(application, exposedByDefaultFlag) {
|
||||
log.Debugf("Filtering disabled marathon task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
|
||||
//filter indeterminable task port
|
||||
portIndexLabel := (*application.Labels)["traefik.portIndex"]
|
||||
portValueLabel := (*application.Labels)["traefik.port"]
|
||||
if portIndexLabel != "" && portValueLabel != "" {
|
||||
log.Debugf("Filtering marathon task %s specifying both traefik.portIndex and traefik.port labels", task.AppID)
|
||||
return false
|
||||
}
|
||||
if portIndexLabel == "" && portValueLabel == "" && len(application.Ports) > 1 {
|
||||
log.Debugf("Filtering marathon task %s with more than 1 port and no traefik.portIndex or traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
if portIndexLabel != "" {
|
||||
index, err := strconv.Atoi((*application.Labels)["traefik.portIndex"])
|
||||
if err != nil || index < 0 || index > len(application.Ports)-1 {
|
||||
log.Debugf("Filtering marathon task %s with unexpected value for traefik.portIndex label", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if portValueLabel != "" {
|
||||
port, err := strconv.Atoi((*application.Labels)["traefik.port"])
|
||||
if err != nil {
|
||||
log.Debugf("Filtering marathon task %s with unexpected value for traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
|
||||
var foundPort bool
|
||||
for _, exposedPort := range task.Ports {
|
||||
if port == exposedPort {
|
||||
foundPort = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundPort {
|
||||
log.Debugf("Filtering marathon task %s without a matching port for traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//filter healthchecks
|
||||
if application.HasHealthChecks() {
|
||||
if task.HasHealthCheckResults() {
|
||||
for _, healthcheck := range task.HealthCheckResults {
|
||||
// found one bad healthcheck, return false
|
||||
if !healthcheck.Alive {
|
||||
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Marathon) applicationFilter(app marathon.Application, filteredTasks []marathon.Task) bool {
|
||||
label, _ := provider.getLabel(app, "traefik.tags")
|
||||
constraintTags := strings.Split(label, ",")
|
||||
if provider.MarathonLBCompatibility {
|
||||
if label, err := provider.getLabel(app, "HAPROXY_GROUP"); err == nil {
|
||||
constraintTags = append(constraintTags, label)
|
||||
}
|
||||
}
|
||||
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
|
||||
if failingConstraint != nil {
|
||||
log.Debugf("Application %v pruned by '%v' constraint", app.ID, failingConstraint.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return fun.Exists(func(task marathon.Task) bool {
|
||||
return task.AppID == app.ID
|
||||
}, filteredTasks)
|
||||
}
|
||||
|
||||
func getApplication(task marathon.Task, apps []marathon.Application) (marathon.Application, error) {
|
||||
for _, application := range apps {
|
||||
if application.ID == task.AppID {
|
||||
return application, nil
|
||||
}
|
||||
}
|
||||
return marathon.Application{}, errors.New("Application not found: " + task.AppID)
|
||||
}
|
||||
|
||||
func isApplicationEnabled(application marathon.Application, exposedByDefault bool) bool {
|
||||
return exposedByDefault && (*application.Labels)["traefik.enable"] != "false" || (*application.Labels)["traefik.enable"] == "true"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getLabel(application marathon.Application, label string) (string, error) {
|
||||
for key, value := range *application.Labels {
|
||||
if key == label {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Label not found:" + label)
|
||||
}
|
||||
|
||||
func (provider *Marathon) getPort(task marathon.Task, applications []marathon.Application) string {
|
||||
application, err := getApplication(task, applications)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return ""
|
||||
}
|
||||
|
||||
if portIndexLabel, err := provider.getLabel(application, "traefik.portIndex"); err == nil {
|
||||
if index, err := strconv.Atoi(portIndexLabel); err == nil {
|
||||
return strconv.Itoa(task.Ports[index])
|
||||
}
|
||||
}
|
||||
if portValueLabel, err := provider.getLabel(application, "traefik.port"); err == nil {
|
||||
return portValueLabel
|
||||
}
|
||||
|
||||
for _, port := range task.Ports {
|
||||
return strconv.Itoa(port)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (provider *Marathon) getWeight(task marathon.Task, applications []marathon.Application) string {
|
||||
application, errApp := getApplication(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return "0"
|
||||
}
|
||||
if label, err := provider.getLabel(application, "traefik.weight"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getDomain(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.domain"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Marathon) getProtocol(task marathon.Task, applications []marathon.Application) string {
|
||||
application, errApp := getApplication(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return "http"
|
||||
}
|
||||
if label, err := provider.getLabel(application, "traefik.protocol"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getSticky(application marathon.Application) string {
|
||||
if sticky, err := provider.getLabel(application, "traefik.backend.loadbalancer.sticky"); err == nil {
|
||||
return sticky
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getPassHostHeader(application marathon.Application) string {
|
||||
if passHostHeader, err := provider.getLabel(application, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "true"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getPriority(application marathon.Application) string {
|
||||
if priority, err := provider.getLabel(application, "traefik.frontend.priority"); err == nil {
|
||||
return priority
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getEntryPoints(application marathon.Application) []string {
|
||||
if entryPoints, err := provider.getLabel(application, "traefik.frontend.entryPoints"); err == nil {
|
||||
return strings.Split(entryPoints, ",")
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// 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 (provider *Marathon) getFrontendRule(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
if provider.MarathonLBCompatibility {
|
||||
if label, err := provider.getLabel(application, "HAPROXY_0_VHOST"); err == nil {
|
||||
return "Host:" + label
|
||||
}
|
||||
}
|
||||
return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
|
||||
application, errApp := getApplication(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return ""
|
||||
}
|
||||
return provider.getFrontendBackend(application)
|
||||
}
|
||||
|
||||
func (provider *Marathon) getFrontendBackend(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.backend"); err == nil {
|
||||
return label
|
||||
}
|
||||
return replace("/", "-", application.ID)
|
||||
}
|
||||
|
||||
func (provider *Marathon) getSubDomain(name string) string {
|
||||
if provider.GroupsAsSubDomains {
|
||||
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(splitedName)))
|
||||
reverseName := strings.Join(splitedName, ".")
|
||||
return reverseName
|
||||
}
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
||||
func (provider *Marathon) hasCircuitBreakerLabels(application marathon.Application) bool {
|
||||
if _, err := provider.getLabel(application, "traefik.backend.circuitbreaker.expression"); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Marathon) hasLoadBalancerLabels(application marathon.Application) bool {
|
||||
_, errMethod := provider.getLabel(application, "traefik.backend.loadbalancer.method")
|
||||
_, errSticky := provider.getLabel(application, "traefik.backend.loadbalancer.sticky")
|
||||
if errMethod != nil && errSticky != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Marathon) hasMaxConnLabels(application marathon.Application) bool {
|
||||
if _, err := provider.getLabel(application, "traefik.backend.maxconn.amount"); err != nil {
|
||||
return false
|
||||
}
|
||||
if _, err := provider.getLabel(application, "traefik.backend.maxconn.extractorfunc"); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Marathon) getMaxConnAmount(application marathon.Application) int64 {
|
||||
if label, err := provider.getLabel(application, "traefik.backend.maxconn.amount"); err == nil {
|
||||
i, errConv := strconv.ParseInt(label, 10, 64)
|
||||
if errConv != nil {
|
||||
log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label)
|
||||
return math.MaxInt64
|
||||
}
|
||||
return i
|
||||
}
|
||||
return math.MaxInt64
|
||||
}
|
||||
|
||||
func (provider *Marathon) getMaxConnExtractorFunc(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.backend.maxconn.extractorfunc"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "request.host"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getLoadBalancerMethod(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.backend.loadbalancer.method"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "wrr"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getCircuitBreakerExpression(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.backend.circuitbreaker.expression"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "NetworkErrorRatio() > 1"
|
||||
}
|
447
integration/vendor/github.com/containous/traefik/provider/mesos.go
generated
vendored
Normal file
447
integration/vendor/github.com/containous/traefik/provider/mesos.go
generated
vendored
Normal file
|
@ -0,0 +1,447 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"fmt"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/mesos/mesos-go/detector"
|
||||
_ "github.com/mesos/mesos-go/detector/zoo" // Registers the ZK detector
|
||||
"github.com/mesosphere/mesos-dns/detect"
|
||||
"github.com/mesosphere/mesos-dns/logging"
|
||||
"github.com/mesosphere/mesos-dns/records"
|
||||
"github.com/mesosphere/mesos-dns/records/state"
|
||||
"github.com/mesosphere/mesos-dns/util"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ Provider = (*Mesos)(nil)
|
||||
|
||||
//Mesos holds configuration of the mesos provider.
|
||||
type Mesos struct {
|
||||
BaseProvider
|
||||
Endpoint string `description:"Mesos server endpoint. You can also specify multiple endpoint for Mesos"`
|
||||
Domain string `description:"Default domain used"`
|
||||
ExposedByDefault bool `description:"Expose Mesos apps by default"`
|
||||
GroupsAsSubDomains bool `description:"Convert Mesos groups to subdomains"`
|
||||
ZkDetectionTimeout int `description:"Zookeeper timeout (in seconds)"`
|
||||
RefreshSeconds int `description:"Polling interval (in seconds)"`
|
||||
IPSources string `description:"IPSources (e.g. host, docker, mesos, rkt)"` // e.g. "host", "docker", "mesos", "rkt"
|
||||
StateTimeoutSecond int `description:"HTTP Timeout (in seconds)"`
|
||||
Masters []string
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Mesos) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
operation := func() error {
|
||||
|
||||
// initialize logging
|
||||
logging.SetupLogs()
|
||||
|
||||
log.Debugf("%s", provider.IPSources)
|
||||
|
||||
var zk string
|
||||
var masters []string
|
||||
|
||||
if strings.HasPrefix(provider.Endpoint, "zk://") {
|
||||
zk = provider.Endpoint
|
||||
} else {
|
||||
masters = strings.Split(provider.Endpoint, ",")
|
||||
}
|
||||
|
||||
errch := make(chan error)
|
||||
|
||||
changed := detectMasters(zk, masters)
|
||||
reload := time.NewTicker(time.Second * time.Duration(provider.RefreshSeconds))
|
||||
zkTimeout := time.Second * time.Duration(provider.ZkDetectionTimeout)
|
||||
timeout := time.AfterFunc(zkTimeout, func() {
|
||||
if zkTimeout > 0 {
|
||||
errch <- fmt.Errorf("master detection timed out after %s", zkTimeout)
|
||||
}
|
||||
})
|
||||
|
||||
defer reload.Stop()
|
||||
defer util.HandleCrash()
|
||||
|
||||
if !provider.Watch {
|
||||
reload.Stop()
|
||||
timeout.Stop()
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-reload.C:
|
||||
configuration := provider.loadMesosConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "mesos",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
case masters := <-changed:
|
||||
if len(masters) == 0 || masters[0] == "" {
|
||||
// no leader
|
||||
timeout.Reset(zkTimeout)
|
||||
} else {
|
||||
timeout.Stop()
|
||||
}
|
||||
log.Debugf("new masters detected: %v", masters)
|
||||
provider.Masters = masters
|
||||
configuration := provider.loadMesosConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "mesos",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
case err := <-errch:
|
||||
log.Errorf("%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("mesos connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to mesos server %+v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Mesos) loadMesosConfig() *types.Configuration {
|
||||
var mesosFuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getPort": provider.getPort,
|
||||
"getHost": provider.getHost,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getFrontendBackend": provider.getFrontendBackend,
|
||||
"getID": provider.getID,
|
||||
"getFrontEndName": provider.getFrontEndName,
|
||||
"replace": replace,
|
||||
}
|
||||
|
||||
t := records.NewRecordGenerator(time.Duration(provider.StateTimeoutSecond) * time.Second)
|
||||
sj, err := t.FindMaster(provider.Masters...)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for mesos, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
tasks := provider.taskRecords(sj)
|
||||
|
||||
//filter tasks
|
||||
filteredTasks := fun.Filter(func(task state.Task) bool {
|
||||
return mesosTaskFilter(task, provider.ExposedByDefault)
|
||||
}, tasks).([]state.Task)
|
||||
|
||||
filteredApps := []state.Task{}
|
||||
for _, value := range filteredTasks {
|
||||
if !taskInSlice(value, filteredApps) {
|
||||
filteredApps = append(filteredApps, value)
|
||||
}
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
Applications []state.Task
|
||||
Tasks []state.Task
|
||||
Domain string
|
||||
}{
|
||||
filteredApps,
|
||||
filteredTasks,
|
||||
provider.Domain,
|
||||
}
|
||||
|
||||
configuration, err := provider.getConfiguration("templates/mesos.tmpl", mesosFuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
func taskInSlice(a state.Task, list []state.Task) bool {
|
||||
for _, b := range list {
|
||||
if b.DiscoveryInfo.Name == a.DiscoveryInfo.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// labels returns all given Status.[]Labels' values whose keys are equal
|
||||
// to the given key
|
||||
func labels(task state.Task, key string) string {
|
||||
for _, l := range task.Labels {
|
||||
if l.Key == key {
|
||||
return l.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func mesosTaskFilter(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 !isMesosApplicationEnabled(task, exposedByDefaultFlag) {
|
||||
log.Debugf("Filtering disabled mesos task %s", task.DiscoveryInfo.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
//filter indeterminable task port
|
||||
portIndexLabel := labels(task, "traefik.portIndex")
|
||||
portValueLabel := labels(task, "traefik.port")
|
||||
if portIndexLabel != "" && portValueLabel != "" {
|
||||
log.Debugf("Filtering mesos task %s specifying both traefik.portIndex and traefik.port labels", task.Name)
|
||||
return false
|
||||
}
|
||||
if portIndexLabel == "" && portValueLabel == "" && len(task.DiscoveryInfo.Ports.DiscoveryPorts) > 1 {
|
||||
log.Debugf("Filtering mesos task %s with more than 1 port and no traefik.portIndex or traefik.port label", task.Name)
|
||||
return false
|
||||
}
|
||||
if portIndexLabel != "" {
|
||||
index, err := strconv.Atoi(labels(task, "traefik.portIndex"))
|
||||
if err != nil || index < 0 || index > len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1 {
|
||||
log.Debugf("Filtering mesos task %s with unexpected value for traefik.portIndex label", task.Name)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if portValueLabel != "" {
|
||||
port, err := strconv.Atoi(labels(task, "traefik.port"))
|
||||
if err != nil {
|
||||
log.Debugf("Filtering mesos task %s with unexpected value for traefik.port label", task.Name)
|
||||
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 traefik.port label", task.Name)
|
||||
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 getMesos(task state.Task, apps []state.Task) (state.Task, error) {
|
||||
for _, application := range apps {
|
||||
if application.DiscoveryInfo.Name == task.DiscoveryInfo.Name {
|
||||
return application, nil
|
||||
}
|
||||
}
|
||||
return state.Task{}, errors.New("Application not found: " + task.DiscoveryInfo.Name)
|
||||
}
|
||||
|
||||
func isMesosApplicationEnabled(task state.Task, exposedByDefault bool) bool {
|
||||
return exposedByDefault && labels(task, "traefik.enable") != "false" || labels(task, "traefik.enable") == "true"
|
||||
}
|
||||
|
||||
func (provider *Mesos) getLabel(task state.Task, label string) (string, error) {
|
||||
for _, tmpLabel := range task.Labels {
|
||||
if tmpLabel.Key == label {
|
||||
return tmpLabel.Value, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Label not found:" + label)
|
||||
}
|
||||
|
||||
func (provider *Mesos) getPort(task state.Task, applications []state.Task) string {
|
||||
application, err := getMesos(task, applications)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get mesos application from task %s", task.DiscoveryInfo.Name)
|
||||
return ""
|
||||
}
|
||||
|
||||
if portIndexLabel, err := provider.getLabel(application, "traefik.portIndex"); err == nil {
|
||||
if index, err := strconv.Atoi(portIndexLabel); err == nil {
|
||||
return strconv.Itoa(task.DiscoveryInfo.Ports.DiscoveryPorts[index].Number)
|
||||
}
|
||||
}
|
||||
if portValueLabel, err := provider.getLabel(application, "traefik.port"); err == nil {
|
||||
return portValueLabel
|
||||
}
|
||||
|
||||
for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts {
|
||||
return strconv.Itoa(port.Number)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (provider *Mesos) getWeight(task state.Task, applications []state.Task) string {
|
||||
application, errApp := getMesos(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get mesos application from task %s", task.DiscoveryInfo.Name)
|
||||
return "0"
|
||||
}
|
||||
|
||||
if label, err := provider.getLabel(application, "traefik.weight"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Mesos) getDomain(task state.Task) string {
|
||||
if label, err := provider.getLabel(task, "traefik.domain"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Mesos) getProtocol(task state.Task, applications []state.Task) string {
|
||||
application, errApp := getMesos(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get mesos application from task %s", task.DiscoveryInfo.Name)
|
||||
return "http"
|
||||
}
|
||||
if label, err := provider.getLabel(application, "traefik.protocol"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (provider *Mesos) getPassHostHeader(task state.Task) string {
|
||||
if passHostHeader, err := provider.getLabel(task, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (provider *Mesos) getPriority(task state.Task) string {
|
||||
if priority, err := provider.getLabel(task, "traefik.frontend.priority"); err == nil {
|
||||
return priority
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Mesos) getEntryPoints(task state.Task) []string {
|
||||
if entryPoints, err := provider.getLabel(task, "traefik.frontend.entryPoints"); err == nil {
|
||||
return strings.Split(entryPoints, ",")
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// 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 (provider *Mesos) getFrontendRule(task state.Task) string {
|
||||
if label, err := provider.getLabel(task, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host:" + strings.ToLower(strings.Replace(provider.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Mesos) getBackend(task state.Task, applications []state.Task) string {
|
||||
application, errApp := getMesos(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get mesos application from task %s", task.DiscoveryInfo.Name)
|
||||
return ""
|
||||
}
|
||||
return provider.getFrontendBackend(application)
|
||||
}
|
||||
|
||||
func (provider *Mesos) getFrontendBackend(task state.Task) string {
|
||||
if label, err := provider.getLabel(task, "traefik.backend"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "-" + cleanupSpecialChars(task.DiscoveryInfo.Name)
|
||||
}
|
||||
|
||||
func (provider *Mesos) getHost(task state.Task) string {
|
||||
return task.IP(strings.Split(provider.IPSources, ",")...)
|
||||
}
|
||||
|
||||
func (provider *Mesos) getID(task state.Task) string {
|
||||
return cleanupSpecialChars(task.ID)
|
||||
}
|
||||
|
||||
func (provider *Mesos) getFrontEndName(task state.Task) string {
|
||||
return strings.Replace(cleanupSpecialChars(task.ID), "/", "-", -1)
|
||||
}
|
||||
|
||||
func cleanupSpecialChars(s string) string {
|
||||
return strings.Replace(strings.Replace(strings.Replace(s, ".", "-", -1), ":", "-", -1), "_", "-", -1)
|
||||
}
|
||||
|
||||
func detectMasters(zk string, masters []string) <-chan []string {
|
||||
changed := make(chan []string, 1)
|
||||
if zk != "" {
|
||||
log.Debugf("Starting master detector for ZK ", zk)
|
||||
if md, err := detector.New(zk); err != nil {
|
||||
log.Errorf("failed to create master detector: %v", err)
|
||||
} else if err := md.Detect(detect.NewMasters(masters, changed)); err != nil {
|
||||
log.Errorf("failed to initialize master detector: %v", err)
|
||||
}
|
||||
} else {
|
||||
changed <- masters
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
func (provider *Mesos) taskRecords(sj state.State) []state.Task {
|
||||
var p []state.Task // == nil
|
||||
for _, f := range sj.Frameworks {
|
||||
for _, task := range f.Tasks {
|
||||
for _, slave := range sj.Slaves {
|
||||
if task.SlaveID == slave.ID {
|
||||
task.SlaveIP = slave.Hostname
|
||||
}
|
||||
}
|
||||
|
||||
// only do running and discoverable tasks
|
||||
if task.State == "TASK_RUNNING" {
|
||||
p = append(p, task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// ErrorFunction A function definition that returns an error
|
||||
// to be passed to the Ignore or Panic error handler
|
||||
type ErrorFunction func() error
|
||||
|
||||
// Ignore Calls an ErrorFunction, and ignores the result.
|
||||
// This allows us to be more explicit when there is no error
|
||||
// handling to be done, for example in defers
|
||||
func Ignore(f ErrorFunction) {
|
||||
_ = f()
|
||||
}
|
||||
func (provider *Mesos) getSubDomain(name string) string {
|
||||
if provider.GroupsAsSubDomains {
|
||||
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(splitedName)))
|
||||
reverseName := strings.Join(splitedName, ".")
|
||||
return reverseName
|
||||
}
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
164
integration/vendor/github.com/containous/traefik/provider/provider.go
generated
vendored
Normal file
164
integration/vendor/github.com/containous/traefik/provider/provider.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/containous/traefik/autogen"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// Provider defines methods of a provider.
|
||||
type Provider interface {
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error
|
||||
}
|
||||
|
||||
// BaseProvider should be inherited by providers
|
||||
type BaseProvider struct {
|
||||
Watch bool `description:"Watch provider"`
|
||||
Filename string `description:"Override default configuration template. For advanced users :)"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags."`
|
||||
}
|
||||
|
||||
// MatchConstraints must match with EVERY single contraint
|
||||
// returns first constraint that do not match or nil
|
||||
func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint) {
|
||||
// if there is no tags and no contraints, filtering is disabled
|
||||
if len(tags) == 0 && len(p.Constraints) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
for _, constraint := range p.Constraints {
|
||||
// xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint
|
||||
if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch {
|
||||
return false, &constraint
|
||||
}
|
||||
}
|
||||
|
||||
// If no constraint or every constraints matching
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
)
|
||||
configuration := new(types.Configuration)
|
||||
tmpl := template.New(p.Filename).Funcs(funcMap)
|
||||
if len(p.Filename) > 0 {
|
||||
buf, err = ioutil.ReadFile(p.Filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
buf, err = autogen.Asset(defaultTemplateFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_, err = tmpl.Parse(string(buf))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var renderedTemplate = buffer.String()
|
||||
// log.Debugf("Rendering results of %s:\n%s", defaultTemplateFile, renderedTemplate)
|
||||
if _, err := toml.Decode(renderedTemplate, configuration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configuration, nil
|
||||
}
|
||||
|
||||
func replace(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
}
|
||||
|
||||
func normalize(name string) string {
|
||||
fargs := func(c rune) bool {
|
||||
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
|
||||
}
|
||||
// get function
|
||||
return strings.Join(strings.FieldsFunc(name, fargs), "-")
|
||||
}
|
||||
|
||||
// ClientTLS holds TLS specific configurations as client
|
||||
// CA, Cert and Key can be either path or file contents
|
||||
type ClientTLS struct {
|
||||
CA string `description:"TLS CA"`
|
||||
Cert string `description:"TLS cert"`
|
||||
Key string `description:"TLS key"`
|
||||
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
|
||||
}
|
||||
|
||||
// CreateTLSConfig creates a TLS config from ClientTLS structures
|
||||
func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
|
||||
var err error
|
||||
if clientTLS == nil {
|
||||
log.Warnf("clientTLS is nil")
|
||||
return nil, nil
|
||||
}
|
||||
caPool := x509.NewCertPool()
|
||||
if clientTLS.CA != "" {
|
||||
var ca []byte
|
||||
if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
|
||||
ca, err = ioutil.ReadFile(clientTLS.CA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to read CA. %s", err)
|
||||
}
|
||||
} else {
|
||||
ca = []byte(clientTLS.CA)
|
||||
}
|
||||
caPool.AppendCertsFromPEM(ca)
|
||||
}
|
||||
|
||||
cert := tls.Certificate{}
|
||||
_, errKeyIsFile := os.Stat(clientTLS.Key)
|
||||
|
||||
if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil {
|
||||
if errKeyIsFile == nil {
|
||||
cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to load TLS keypair: %v", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("tls cert is a file, but tls key is not")
|
||||
}
|
||||
} else {
|
||||
if errKeyIsFile != nil {
|
||||
cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to load TLS keypair: %v", err)
|
||||
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("tls key is a file, but tls cert is not")
|
||||
}
|
||||
}
|
||||
|
||||
TLSConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: caPool,
|
||||
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
|
||||
}
|
||||
return TLSConfig, nil
|
||||
}
|
34
integration/vendor/github.com/containous/traefik/provider/zk.go
generated
vendored
Normal file
34
integration/vendor/github.com/containous/traefik/provider/zk.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/zookeeper"
|
||||
)
|
||||
|
||||
var _ Provider = (*Zookepper)(nil)
|
||||
|
||||
// Zookepper holds configurations of the Zookepper provider.
|
||||
type Zookepper struct {
|
||||
Kv `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
provider.kvclient = store
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
||||
// CreateStore creates the KV store
|
||||
func (provider *Zookepper) CreateStore() (store.Store, error) {
|
||||
provider.storeType = store.ZK
|
||||
zookeeper.Register()
|
||||
return provider.createStore()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue