Refactor into dual Rancher API/Metadata providers
Introduces Rancher's metadata service as an optional provider source for Traefik, enabled by setting `rancher.MetadataService`. The provider uses a long polling technique to watch the metadata service and obtain near instantaneous updates. Alternatively it can be configured to poll the metadata service every `rancher.RefreshSeconds` by setting `rancher.MetadataPoll`. The refactor splits API and metadata service code into separate source files respectively, and specific configuration is deferred to sub-structs. Incorporates bugfix #1414
This commit is contained in:
parent
984ea1040f
commit
9cb07d026f
14 changed files with 1006 additions and 272 deletions
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/containous/traefik/cluster"
|
"github.com/containous/traefik/cluster"
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/provider/kubernetes"
|
"github.com/containous/traefik/provider/kubernetes"
|
||||||
|
"github.com/containous/traefik/provider/rancher"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/server"
|
"github.com/containous/traefik/server"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
@ -193,6 +194,28 @@ func run(traefikConfiguration *server.TraefikConfiguration) {
|
||||||
globalConfiguration.DefaultEntryPoints = []string{"http"}
|
globalConfiguration.DefaultEntryPoints = []string{"http"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if globalConfiguration.Rancher != nil {
|
||||||
|
// Ensure backwards compatibility for now
|
||||||
|
if len(globalConfiguration.Rancher.AccessKey) > 0 ||
|
||||||
|
len(globalConfiguration.Rancher.Endpoint) > 0 ||
|
||||||
|
len(globalConfiguration.Rancher.SecretKey) > 0 {
|
||||||
|
|
||||||
|
if globalConfiguration.Rancher.API == nil {
|
||||||
|
globalConfiguration.Rancher.API = &rancher.APIConfiguration{
|
||||||
|
AccessKey: globalConfiguration.Rancher.AccessKey,
|
||||||
|
SecretKey: globalConfiguration.Rancher.SecretKey,
|
||||||
|
Endpoint: globalConfiguration.Rancher.Endpoint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Warn("Deprecated configuration found: rancher.[accesskey|secretkey|endpoint]. " +
|
||||||
|
"Please use rancher.api.[accesskey|secretkey|endpoint] instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalConfiguration.Rancher.Metadata != nil && len(globalConfiguration.Rancher.Metadata.Prefix) == 0 {
|
||||||
|
globalConfiguration.Rancher.Metadata.Prefix = "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if globalConfiguration.Debug {
|
if globalConfiguration.Debug {
|
||||||
globalConfiguration.LogLevel = "DEBUG"
|
globalConfiguration.LogLevel = "DEBUG"
|
||||||
}
|
}
|
||||||
|
|
6
glide.lock
generated
6
glide.lock
generated
|
@ -1,4 +1,4 @@
|
||||||
hash: 34ceb7bd979d43efdbf721ccb9d983061c06db527148f90f1784db89f6d089f0
|
hash: 088194c8357ca08e27476866b9007adfa7711500fe0c78650ecb397c4f70075a
|
||||||
updated: 2017-05-19T23:30:19.890844996+02:00
|
updated: 2017-05-19T23:30:19.890844996+02:00
|
||||||
imports:
|
imports:
|
||||||
- name: cloud.google.com/go
|
- name: cloud.google.com/go
|
||||||
|
@ -364,6 +364,10 @@ imports:
|
||||||
version: 5b8f6cc26b355ba03d7611fce3844155b7baf05b
|
version: 5b8f6cc26b355ba03d7611fce3844155b7baf05b
|
||||||
subpackages:
|
subpackages:
|
||||||
- client
|
- client
|
||||||
|
- name: github.com/rancher/go-rancher-metadata
|
||||||
|
version: 95d4962a8f0420be24fb49c2cb4f5491284c62f1
|
||||||
|
subpackages:
|
||||||
|
- metadata
|
||||||
- name: github.com/ryanuber/go-glob
|
- name: github.com/ryanuber/go-glob
|
||||||
version: 256dc444b735e061061cf46c809487313d5b0065
|
version: 256dc444b735e061061cf46c809487313d5b0065
|
||||||
- name: github.com/samuel/go-zookeeper
|
- name: github.com/samuel/go-zookeeper
|
||||||
|
|
|
@ -163,6 +163,8 @@ import:
|
||||||
version: 5b8f6cc26b355ba03d7611fce3844155b7baf05b
|
version: 5b8f6cc26b355ba03d7611fce3844155b7baf05b
|
||||||
- package: golang.org/x/oauth2
|
- package: golang.org/x/oauth2
|
||||||
version: 7fdf09982454086d5570c7db3e11f360194830ca
|
version: 7fdf09982454086d5570c7db3e11f360194830ca
|
||||||
|
- package: github.com/rancher/go-rancher-metadata
|
||||||
|
version: 95d4962a8f0420be24fb49c2cb4f5491284c62f1
|
||||||
subpackages:
|
subpackages:
|
||||||
- google
|
- google
|
||||||
- package: github.com/googleapis/gax-go
|
- package: github.com/googleapis/gax-go
|
||||||
|
|
234
provider/rancher/api.go
Normal file
234
provider/rancher/api.go
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
package rancher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenk/backoff"
|
||||||
|
"github.com/containous/traefik/job"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
|
||||||
|
rancher "github.com/rancher/go-rancher/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
withoutPagination *rancher.ListOpts
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIConfiguration contains configuration properties specific to the Rancher
|
||||||
|
// API provider.
|
||||||
|
type APIConfiguration struct {
|
||||||
|
Endpoint string `description:"Rancher server API HTTP(S) endpoint"`
|
||||||
|
AccessKey string `description:"Rancher server API access key"`
|
||||||
|
SecretKey string `description:"Rancher server API secret key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
withoutPagination = &rancher.ListOpts{
|
||||||
|
Filters: map[string]interface{}{"limit": 0},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) createClient() (*rancher.RancherClient, error) {
|
||||||
|
rancherURL := getenv("CATTLE_URL", p.API.Endpoint)
|
||||||
|
accessKey := getenv("CATTLE_ACCESS_KEY", p.API.AccessKey)
|
||||||
|
secretKey := getenv("CATTLE_SECRET_KEY", p.API.SecretKey)
|
||||||
|
|
||||||
|
return rancher.NewRancherClient(&rancher.ClientOpts{
|
||||||
|
Url: rancherURL,
|
||||||
|
AccessKey: accessKey,
|
||||||
|
SecretKey: secretKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getenv(key, fallback string) string {
|
||||||
|
value := os.Getenv(key)
|
||||||
|
if len(value) == 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) apiProvide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||||
|
p.Constraints = append(p.Constraints, constraints...)
|
||||||
|
|
||||||
|
if p.API == nil {
|
||||||
|
p.API = &APIConfiguration{}
|
||||||
|
}
|
||||||
|
|
||||||
|
safe.Go(func() {
|
||||||
|
operation := func() error {
|
||||||
|
rancherClient, err := p.createClient()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create a client for rancher, error: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
var environments = listRancherEnvironments(rancherClient)
|
||||||
|
var services = listRancherServices(rancherClient)
|
||||||
|
var container = listRancherContainer(rancherClient)
|
||||||
|
|
||||||
|
var rancherData = parseAPISourcedRancherData(environments, services, container)
|
||||||
|
|
||||||
|
configuration := p.loadRancherConfig(rancherData)
|
||||||
|
configurationChan <- types.ConfigMessage{
|
||||||
|
ProviderName: "rancher",
|
||||||
|
Configuration: configuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Watch {
|
||||||
|
_, cancel := context.WithCancel(ctx)
|
||||||
|
ticker := time.NewTicker(time.Second * time.Duration(p.RefreshSeconds))
|
||||||
|
pool.Go(func(stop chan bool) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
|
||||||
|
log.Debugf("Refreshing new Data from Provider API")
|
||||||
|
var environments = listRancherEnvironments(rancherClient)
|
||||||
|
var services = listRancherServices(rancherClient)
|
||||||
|
var container = listRancherContainer(rancherClient)
|
||||||
|
|
||||||
|
rancherData := parseAPISourcedRancherData(environments, services, container)
|
||||||
|
|
||||||
|
configuration := p.loadRancherConfig(rancherData)
|
||||||
|
if configuration != nil {
|
||||||
|
configurationChan <- types.ConfigMessage{
|
||||||
|
ProviderName: "rancher",
|
||||||
|
Configuration: configuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-stop:
|
||||||
|
ticker.Stop()
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.Errorf("Provider 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 Provider Endpoint %+v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listRancherEnvironments(client *rancher.RancherClient) []*rancher.Project {
|
||||||
|
|
||||||
|
// Rancher Environment in frontend UI is actually project in API
|
||||||
|
// https://forums.rancher.com/t/api-key-for-all-environments/279/9
|
||||||
|
|
||||||
|
var environmentList = []*rancher.Project{}
|
||||||
|
|
||||||
|
environments, err := client.Project.List(nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot get Rancher Environments %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range environments.Data {
|
||||||
|
environmentList = append(environmentList, &environments.Data[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
return environmentList
|
||||||
|
}
|
||||||
|
|
||||||
|
func listRancherServices(client *rancher.RancherClient) []*rancher.Service {
|
||||||
|
|
||||||
|
var servicesList = []*rancher.Service{}
|
||||||
|
|
||||||
|
services, err := client.Service.List(withoutPagination)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot get Provider Services %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range services.Data {
|
||||||
|
servicesList = append(servicesList, &services.Data[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
return servicesList
|
||||||
|
}
|
||||||
|
|
||||||
|
func listRancherContainer(client *rancher.RancherClient) []*rancher.Container {
|
||||||
|
|
||||||
|
containerList := []*rancher.Container{}
|
||||||
|
|
||||||
|
container, err := client.Container.List(withoutPagination)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot get Provider Services %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
valid := true
|
||||||
|
|
||||||
|
for valid {
|
||||||
|
for k := range container.Data {
|
||||||
|
containerList = append(containerList, &container.Data[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err = container.Next()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if container == nil || len(container.Data) == 0 {
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return containerList
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAPISourcedRancherData(environments []*rancher.Project, services []*rancher.Service, containers []*rancher.Container) []rancherData {
|
||||||
|
var rancherDataList []rancherData
|
||||||
|
|
||||||
|
for _, environment := range environments {
|
||||||
|
|
||||||
|
for _, service := range services {
|
||||||
|
if service.EnvironmentId != environment.Id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rancherData := rancherData{
|
||||||
|
Name: environment.Name + "/" + service.Name,
|
||||||
|
Health: service.HealthState,
|
||||||
|
State: service.State,
|
||||||
|
Labels: make(map[string]string),
|
||||||
|
Containers: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.LaunchConfig == nil || service.LaunchConfig.Labels == nil {
|
||||||
|
log.Warnf("Rancher Service Labels are missing. Environment: %s, service: %s", environment.Name, service.Name)
|
||||||
|
} else {
|
||||||
|
for key, value := range service.LaunchConfig.Labels {
|
||||||
|
rancherData.Labels[key] = value.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
if container.Labels["io.rancher.stack_service.name"] == rancherData.Name &&
|
||||||
|
containerFilter(container.Name, container.HealthState, container.State) {
|
||||||
|
rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rancherDataList = append(rancherDataList, rancherData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rancherDataList
|
||||||
|
}
|
136
provider/rancher/metadata.go
Normal file
136
provider/rancher/metadata.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package rancher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
|
||||||
|
rancher "github.com/rancher/go-rancher-metadata/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetadataConfiguration contains configuration properties specific to
|
||||||
|
// the Rancher metadata service provider.
|
||||||
|
type MetadataConfiguration struct {
|
||||||
|
IntervalPoll bool `description:"Poll the Rancher metadata service every 'rancher.refreshseconds' (less accurate)"`
|
||||||
|
Prefix string `description:"Prefix used for accessing the Rancher metadata service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) metadataProvide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||||
|
p.Constraints = append(p.Constraints, constraints...)
|
||||||
|
|
||||||
|
metadataServiceURL := fmt.Sprintf("http://rancher-metadata.rancher.internal/%s", p.Metadata.Prefix)
|
||||||
|
|
||||||
|
safe.Go(func() {
|
||||||
|
operation := func() error {
|
||||||
|
client, err := rancher.NewClientAndWait(metadataServiceURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("Failed to create Rancher metadata service client: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfiguration := func(version string) {
|
||||||
|
log.WithField("metadata_version", version).Debugln("Refreshing configuration from Rancher metadata service")
|
||||||
|
|
||||||
|
services, err := client.GetServices()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to query Rancher metadata service: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rancherData := parseMetadataSourcedRancherData(services)
|
||||||
|
configuration := p.loadRancherConfig(rancherData)
|
||||||
|
configurationChan <- types.ConfigMessage{
|
||||||
|
ProviderName: "rancher",
|
||||||
|
Configuration: configuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateConfiguration("init")
|
||||||
|
|
||||||
|
if p.Watch {
|
||||||
|
pool.Go(func(stop chan bool) {
|
||||||
|
switch {
|
||||||
|
case p.Metadata.IntervalPoll:
|
||||||
|
p.intervalPoll(client, updateConfiguration, stop)
|
||||||
|
default:
|
||||||
|
p.longPoll(client, updateConfiguration, stop)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"error": err,
|
||||||
|
"retry_in": time,
|
||||||
|
}).Errorln("Rancher metadata service connection error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify); err != nil {
|
||||||
|
log.WithField("endpoint", metadataServiceURL).Errorln("Cannot connect to Rancher metadata service")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) intervalPoll(client rancher.Client, updateConfiguration func(string), stop chan bool) {
|
||||||
|
_, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Duration(p.RefreshSeconds))
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
var version string
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
newVersion, err := client.GetVersion()
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("error", err).Errorln("Failed to read Rancher metadata service version")
|
||||||
|
} else if version != newVersion {
|
||||||
|
version = newVersion
|
||||||
|
updateConfiguration(version)
|
||||||
|
}
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) longPoll(client rancher.Client, updateConfiguration func(string), stop chan bool) {
|
||||||
|
_, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Holds the connection until there is either a change in the metadata
|
||||||
|
// repository or `p.RefreshSeconds` has elapsed. Long polling should be
|
||||||
|
// favoured for the most accurate configuration updates.
|
||||||
|
go client.OnChange(p.RefreshSeconds, updateConfiguration)
|
||||||
|
<-stop
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMetadataSourcedRancherData(services []rancher.Service) (rancherDataList []rancherData) {
|
||||||
|
for _, service := range services {
|
||||||
|
var containerIPAddresses []string
|
||||||
|
for _, container := range service.Containers {
|
||||||
|
if containerFilter(container.Name, container.HealthState, container.State) {
|
||||||
|
containerIPAddresses = append(containerIPAddresses, container.PrimaryIp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rancherDataList = append(rancherDataList, rancherData{
|
||||||
|
Name: service.Name,
|
||||||
|
State: service.State,
|
||||||
|
Labels: service.Labels,
|
||||||
|
Containers: containerIPAddresses,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rancherDataList
|
||||||
|
}
|
|
@ -1,28 +1,17 @@
|
||||||
package rancher
|
package rancher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
"github.com/cenk/backoff"
|
|
||||||
"github.com/containous/traefik/job"
|
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
rancher "github.com/rancher/go-rancher/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
withoutPagination *rancher.ListOpts
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ provider.Provider = (*Provider)(nil)
|
var _ provider.Provider = (*Provider)(nil)
|
||||||
|
@ -30,13 +19,13 @@ var _ provider.Provider = (*Provider)(nil)
|
||||||
// Provider holds configurations of the provider.
|
// Provider holds configurations of the provider.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
provider.BaseProvider `mapstructure:",squash"`
|
provider.BaseProvider `mapstructure:",squash"`
|
||||||
Endpoint string `description:"Rancher server HTTP(S) endpoint."`
|
APIConfiguration `mapstructure:",squash"` // Provide backwards compatibility
|
||||||
AccessKey string `description:"Rancher server access key."`
|
API *APIConfiguration `description:"Enable the Rancher API provider"`
|
||||||
SecretKey string `description:"Rancher server Secret Key."`
|
Metadata *MetadataConfiguration `description:"Enable the Rancher metadata service provider"`
|
||||||
ExposedByDefault bool `description:"Expose Services by default"`
|
|
||||||
Domain string `description:"Default domain used"`
|
Domain string `description:"Default domain used"`
|
||||||
RefreshSeconds int `description:"Polling interval (in seconds)"`
|
RefreshSeconds int `description:"Polling interval (in seconds)"`
|
||||||
EnableServiceHealthFilter bool `description:"Filter services with unhealthy states and health states."`
|
ExposedByDefault bool `description:"Expose services by default"`
|
||||||
|
EnableServiceHealthFilter bool `description:"Filter services with unhealthy states and inactive states"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rancherData struct {
|
type rancherData struct {
|
||||||
|
@ -47,12 +36,6 @@ type rancherData struct {
|
||||||
State string
|
State string
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
withoutPagination = &rancher.ListOpts{
|
|
||||||
Filters: map[string]interface{}{"limit": 0},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r rancherData) String() string {
|
func (r rancherData) String() string {
|
||||||
return fmt.Sprintf("{name:%s, labels:%v, containers: %v, health: %s, state: %s}", r.Name, r.Labels, r.Containers, r.Health, r.State)
|
return fmt.Sprintf("{name:%s, labels:%v, containers: %v, health: %s, state: %s}", r.Name, r.Labels, r.Containers, r.Health, r.State)
|
||||||
}
|
}
|
||||||
|
@ -207,205 +190,16 @@ func getServiceLabel(service rancherData, label string) (string, error) {
|
||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", errors.New("Label not found:" + label)
|
return "", fmt.Errorf("label not found: %s", label)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) createClient() (*rancher.RancherClient, error) {
|
// Provide allows either the Rancher API or metadata service provider to
|
||||||
|
// seed configuration into Traefik using the given configuration channel.
|
||||||
rancherURL := getenv("CATTLE_URL", p.Endpoint)
|
|
||||||
accessKey := getenv("CATTLE_ACCESS_KEY", p.AccessKey)
|
|
||||||
secretKey := getenv("CATTLE_SECRET_KEY", p.SecretKey)
|
|
||||||
|
|
||||||
return rancher.NewRancherClient(&rancher.ClientOpts{
|
|
||||||
Url: rancherURL,
|
|
||||||
AccessKey: accessKey,
|
|
||||||
SecretKey: secretKey,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getenv(key, fallback string) string {
|
|
||||||
value := os.Getenv(key)
|
|
||||||
if len(value) == 0 {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provide allows the rancher provider to provide configurations to traefik
|
|
||||||
// using the given configuration channel.
|
|
||||||
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||||
p.Constraints = append(p.Constraints, constraints...)
|
if p.Metadata == nil {
|
||||||
|
return p.apiProvide(configurationChan, pool, constraints)
|
||||||
safe.Go(func() {
|
|
||||||
operation := func() error {
|
|
||||||
rancherClient, err := p.createClient()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to create a client for rancher, error: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
return p.metadataProvide(configurationChan, pool, constraints)
|
||||||
ctx := context.Background()
|
|
||||||
var environments = listRancherEnvironments(rancherClient)
|
|
||||||
var services = listRancherServices(rancherClient)
|
|
||||||
var container = listRancherContainer(rancherClient)
|
|
||||||
|
|
||||||
var rancherData = parseRancherData(environments, services, container)
|
|
||||||
|
|
||||||
configuration := p.loadRancherConfig(rancherData)
|
|
||||||
configurationChan <- types.ConfigMessage{
|
|
||||||
ProviderName: "rancher",
|
|
||||||
Configuration: configuration,
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Watch {
|
|
||||||
_, cancel := context.WithCancel(ctx)
|
|
||||||
ticker := time.NewTicker(time.Second * time.Duration(p.RefreshSeconds))
|
|
||||||
pool.Go(func(stop chan bool) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
|
|
||||||
log.Debugf("Refreshing new Data from Provider API")
|
|
||||||
var environments = listRancherEnvironments(rancherClient)
|
|
||||||
var services = listRancherServices(rancherClient)
|
|
||||||
var container = listRancherContainer(rancherClient)
|
|
||||||
|
|
||||||
rancherData := parseRancherData(environments, services, container)
|
|
||||||
|
|
||||||
configuration := p.loadRancherConfig(rancherData)
|
|
||||||
if configuration != nil {
|
|
||||||
configurationChan <- types.ConfigMessage{
|
|
||||||
ProviderName: "rancher",
|
|
||||||
Configuration: configuration,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-stop:
|
|
||||||
ticker.Stop()
|
|
||||||
cancel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
notify := func(err error, time time.Duration) {
|
|
||||||
log.Errorf("Provider 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 Provider Endpoint %+v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func listRancherEnvironments(client *rancher.RancherClient) []*rancher.Environment {
|
|
||||||
|
|
||||||
var environmentList = []*rancher.Environment{}
|
|
||||||
|
|
||||||
environments, err := client.Environment.List(withoutPagination)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Cannot get Provider Environments %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range environments.Data {
|
|
||||||
environmentList = append(environmentList, &environments.Data[k])
|
|
||||||
}
|
|
||||||
|
|
||||||
return environmentList
|
|
||||||
}
|
|
||||||
|
|
||||||
func listRancherServices(client *rancher.RancherClient) []*rancher.Service {
|
|
||||||
|
|
||||||
var servicesList = []*rancher.Service{}
|
|
||||||
|
|
||||||
services, err := client.Service.List(withoutPagination)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Cannot get Provider Services %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range services.Data {
|
|
||||||
servicesList = append(servicesList, &services.Data[k])
|
|
||||||
}
|
|
||||||
|
|
||||||
return servicesList
|
|
||||||
}
|
|
||||||
|
|
||||||
func listRancherContainer(client *rancher.RancherClient) []*rancher.Container {
|
|
||||||
|
|
||||||
containerList := []*rancher.Container{}
|
|
||||||
|
|
||||||
container, err := client.Container.List(withoutPagination)
|
|
||||||
|
|
||||||
log.Debugf("first container len: %i", len(container.Data))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Cannot get Provider Services %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
valid := true
|
|
||||||
|
|
||||||
for valid {
|
|
||||||
for k := range container.Data {
|
|
||||||
containerList = append(containerList, &container.Data[k])
|
|
||||||
}
|
|
||||||
|
|
||||||
container, err = container.Next()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if container == nil || len(container.Data) == 0 {
|
|
||||||
valid = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return containerList
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRancherData(environments []*rancher.Environment, services []*rancher.Service, containers []*rancher.Container) []rancherData {
|
|
||||||
var rancherDataList []rancherData
|
|
||||||
|
|
||||||
for _, environment := range environments {
|
|
||||||
|
|
||||||
for _, service := range services {
|
|
||||||
if service.EnvironmentId != environment.Id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rancherData := rancherData{
|
|
||||||
Name: environment.Name + "/" + service.Name,
|
|
||||||
Health: service.HealthState,
|
|
||||||
State: service.State,
|
|
||||||
Labels: make(map[string]string),
|
|
||||||
Containers: []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.LaunchConfig == nil || service.LaunchConfig.Labels == nil {
|
|
||||||
log.Warnf("Rancher Service Labels are missing. Environment: %s, service: %s", environment.Name, service.Name)
|
|
||||||
} else {
|
|
||||||
for key, value := range service.LaunchConfig.Labels {
|
|
||||||
rancherData.Labels[key] = value.(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, container := range containers {
|
|
||||||
if container.Labels["io.rancher.stack_service.name"] == rancherData.Name && containerFilter(container) {
|
|
||||||
rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rancherDataList = append(rancherDataList, rancherData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rancherDataList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuration {
|
func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuration {
|
||||||
|
@ -464,14 +258,14 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerFilter(container *rancher.Container) bool {
|
func containerFilter(name, healthState, state string) bool {
|
||||||
if container.HealthState != "" && container.HealthState != "healthy" && container.HealthState != "updating-healthy" {
|
if healthState != "" && healthState != "healthy" && healthState != "updating-healthy" {
|
||||||
log.Debugf("Filtering container %s with healthState of %s", container.Name, container.HealthState)
|
log.Debugf("Filtering container %s with healthState of %s", name, healthState)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.State != "" && container.State != "running" && container.State != "updating-running" {
|
if state != "" && state != "running" && state != "updating-running" {
|
||||||
log.Debugf("Filtering container %s with state of %s", container.Name, container.State)
|
log.Debugf("Filtering container %s with state of %s", name, state)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
rancher "github.com/rancher/go-rancher/client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRancherServiceFilter(t *testing.T) {
|
func TestRancherServiceFilter(t *testing.T) {
|
||||||
|
@ -114,49 +113,41 @@ func TestRancherServiceFilter(t *testing.T) {
|
||||||
|
|
||||||
func TestRancherContainerFilter(t *testing.T) {
|
func TestRancherContainerFilter(t *testing.T) {
|
||||||
containers := []struct {
|
containers := []struct {
|
||||||
container *rancher.Container
|
name string
|
||||||
|
healthState string
|
||||||
|
state string
|
||||||
expected bool
|
expected bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
container: &rancher.Container{
|
healthState: "unhealthy",
|
||||||
HealthState: "unhealthy",
|
state: "running",
|
||||||
State: "running",
|
|
||||||
},
|
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: &rancher.Container{
|
healthState: "healthy",
|
||||||
HealthState: "healthy",
|
state: "stopped",
|
||||||
State: "stopped",
|
|
||||||
},
|
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: &rancher.Container{
|
state: "stopped",
|
||||||
State: "stopped",
|
|
||||||
},
|
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: &rancher.Container{
|
healthState: "healthy",
|
||||||
HealthState: "healthy",
|
state: "running",
|
||||||
State: "running",
|
|
||||||
},
|
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: &rancher.Container{
|
healthState: "updating-healthy",
|
||||||
HealthState: "updating-healthy",
|
state: "updating-running",
|
||||||
State: "updating-running",
|
|
||||||
},
|
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range containers {
|
for _, container := range containers {
|
||||||
actual := containerFilter(e.container)
|
actual := containerFilter(container.name, container.healthState, container.state)
|
||||||
if actual != e.expected {
|
if actual != container.expected {
|
||||||
t.Fatalf("expected %t, got %t", e.expected, actual)
|
t.Fatalf("expected %t, got %t", container.expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -506,7 +497,7 @@ func TestRancherGetLabel(t *testing.T) {
|
||||||
service: rancherData{
|
service: rancherData{
|
||||||
Name: "test-service",
|
Name: "test-service",
|
||||||
},
|
},
|
||||||
expected: "Label not found",
|
expected: "label not found",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
service: rancherData{
|
service: rancherData{
|
||||||
|
@ -593,9 +584,7 @@ func TestRancherLoadRancherConfig(t *testing.T) {
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
var rancherDataList []rancherData
|
var rancherDataList []rancherData
|
||||||
for _, service := range c.services {
|
rancherDataList = append(rancherDataList, c.services...)
|
||||||
rancherDataList = append(rancherDataList, service)
|
|
||||||
}
|
|
||||||
|
|
||||||
actualConfig := provider.loadRancherConfig(rancherDataList)
|
actualConfig := provider.loadRancherConfig(rancherDataList)
|
||||||
|
|
||||||
|
|
|
@ -450,7 +450,6 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
defaultRancher.Watch = true
|
defaultRancher.Watch = true
|
||||||
defaultRancher.ExposedByDefault = true
|
defaultRancher.ExposedByDefault = true
|
||||||
defaultRancher.RefreshSeconds = 15
|
defaultRancher.RefreshSeconds = 15
|
||||||
defaultRancher.EnableServiceHealthFilter = false
|
|
||||||
|
|
||||||
// default DynamoDB
|
// default DynamoDB
|
||||||
var defaultDynamoDB dynamodb.Provider
|
var defaultDynamoDB dynamodb.Provider
|
||||||
|
@ -485,9 +484,6 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
AccessLog: &defaultAccessLog,
|
AccessLog: &defaultAccessLog,
|
||||||
}
|
}
|
||||||
|
|
||||||
//default Rancher
|
|
||||||
//@TODO: ADD
|
|
||||||
|
|
||||||
return &TraefikConfiguration{
|
return &TraefikConfiguration{
|
||||||
GlobalConfiguration: defaultConfiguration,
|
GlobalConfiguration: defaultConfiguration,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1098,28 +1098,59 @@
|
||||||
#
|
#
|
||||||
# ExposedByDefault = false
|
# ExposedByDefault = false
|
||||||
|
|
||||||
# Filter services with unhealthy states and health states
|
# Filter services with unhealthy states and inactive states
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
# Default: false
|
# Default: false
|
||||||
#
|
#
|
||||||
# EnableServiceHealthFilter = false
|
# EnableServiceHealthFilter = true
|
||||||
|
|
||||||
# Endpoint to use when connecting to Rancher
|
# Enable Rancher API configuration backend
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
# [rancher.api]
|
||||||
|
|
||||||
|
# Endpoint to use when connecting to the Rancher API
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
# Endpoint = "http://rancherserver.example.com/v1"
|
# Endpoint = "http://rancherserver.example.com/v1"
|
||||||
|
|
||||||
# AccessKey to use when connecting to Rancher
|
# AccessKey to use when connecting to the Rancher API
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
# AccessKey = "XXXXXXXXXXXXXXXXXXXX"
|
# AccessKey = "XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
|
||||||
# SecretKey to use when connecting to Rancher
|
# SecretKey to use when connecting to the Rancher API
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
# SecretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
# SecretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
|
||||||
|
# Enable Rancher metadata service configuration backend instead of the API
|
||||||
|
# configuration backend
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# [rancher.metadataservice]
|
||||||
|
|
||||||
|
# Poll the Rancher metadata service for changes every `rancher.RefreshSeconds`
|
||||||
|
# NOTE: this is less accurate than the default long polling technique which
|
||||||
|
# will provide near instantaneous updates to Traefik
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# IntervalPoll = true
|
||||||
|
|
||||||
|
# Prefix used for accessing the Rancher metadata service
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "/latest"
|
||||||
|
#
|
||||||
|
# Prefix = "/2016-07-29"
|
||||||
|
|
||||||
# Constraints
|
# Constraints
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
|
|
31
vendor/github.com/rancher/go-rancher-metadata/main.go
generated
vendored
Normal file
31
vendor/github.com/rancher/go-rancher-metadata/main.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/rancher/go-rancher-metadata/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
metadataUrl = "http://rancher-metadata/2015-12-19"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
m := metadata.NewClient(metadataUrl)
|
||||||
|
|
||||||
|
version := "init"
|
||||||
|
|
||||||
|
for {
|
||||||
|
newVersion, err := m.GetVersion()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Error reading metadata version: %v", err)
|
||||||
|
} else if version == newVersion {
|
||||||
|
logrus.Debug("No changes in metadata version")
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("Metadata version has changed, oldVersion=[%s], newVersion=[%s]", version, newVersion)
|
||||||
|
version = newVersion
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
64
vendor/github.com/rancher/go-rancher-metadata/metadata/change.go
generated
vendored
Normal file
64
vendor/github.com/rancher/go-rancher-metadata/metadata/change.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *client) OnChangeWithError(intervalSeconds int, do func(string)) error {
|
||||||
|
return m.onChangeFromVersionWithError("init", intervalSeconds, do)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) onChangeFromVersionWithError(version string, intervalSeconds int, do func(string)) error {
|
||||||
|
for {
|
||||||
|
newVersion, err := m.waitVersion(intervalSeconds, version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if version == newVersion {
|
||||||
|
logrus.Debug("No changes in metadata version")
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("Metadata Version has been changed. Old version: %s. New version: %s.", version, newVersion)
|
||||||
|
version = newVersion
|
||||||
|
do(newVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) OnChange(intervalSeconds int, do func(string)) {
|
||||||
|
version := "init"
|
||||||
|
updateVersionAndDo := func(v string) {
|
||||||
|
version = v
|
||||||
|
do(version)
|
||||||
|
}
|
||||||
|
interval := time.Duration(intervalSeconds)
|
||||||
|
for {
|
||||||
|
if err := m.onChangeFromVersionWithError(version, intervalSeconds, updateVersionAndDo); err != nil {
|
||||||
|
logrus.Errorf("Error reading metadata version: %v", err)
|
||||||
|
}
|
||||||
|
time.Sleep(interval * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeout interface {
|
||||||
|
Timeout() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) waitVersion(maxWait int, version string) (string, error) {
|
||||||
|
for {
|
||||||
|
resp, err := m.SendRequest(fmt.Sprintf("/version?wait=true&value=%s&maxWait=%d", version, maxWait))
|
||||||
|
if err != nil {
|
||||||
|
t, ok := err.(timeout)
|
||||||
|
if ok && t.Timeout() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(resp, &version)
|
||||||
|
return version, err
|
||||||
|
}
|
||||||
|
}
|
262
vendor/github.com/rancher/go-rancher-metadata/metadata/metadata.go
generated
vendored
Normal file
262
vendor/github.com/rancher/go-rancher-metadata/metadata/metadata.go
generated
vendored
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
OnChangeWithError(int, func(string)) error
|
||||||
|
OnChange(int, func(string))
|
||||||
|
SendRequest(string) ([]byte, error)
|
||||||
|
GetVersion() (string, error)
|
||||||
|
GetSelfHost() (Host, error)
|
||||||
|
GetSelfContainer() (Container, error)
|
||||||
|
GetSelfServiceByName(string) (Service, error)
|
||||||
|
GetSelfService() (Service, error)
|
||||||
|
GetSelfStack() (Stack, error)
|
||||||
|
GetServices() ([]Service, error)
|
||||||
|
GetStacks() ([]Stack, error)
|
||||||
|
GetContainers() ([]Container, error)
|
||||||
|
GetServiceContainers(string, string) ([]Container, error)
|
||||||
|
GetHosts() ([]Host, error)
|
||||||
|
GetHost(string) (Host, error)
|
||||||
|
GetNetworks() ([]Network, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
url string
|
||||||
|
ip string
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(url, ip string) *client {
|
||||||
|
return &client{url, ip, &http.Client{Timeout: 10 * time.Second}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(url string) Client {
|
||||||
|
ip := ""
|
||||||
|
return newClient(url, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientWithIPAndWait(url, ip string) (Client, error) {
|
||||||
|
client := newClient(url, ip)
|
||||||
|
|
||||||
|
if err := testConnection(client); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientAndWait(url string) (Client, error) {
|
||||||
|
ip := ""
|
||||||
|
client := newClient(url, ip)
|
||||||
|
|
||||||
|
if err := testConnection(client); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) SendRequest(path string) ([]byte, error) {
|
||||||
|
req, err := http.NewRequest("GET", m.url+path, nil)
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
|
if m.ip != "" {
|
||||||
|
req.Header.Add("X-Forwarded-For", m.ip)
|
||||||
|
}
|
||||||
|
resp, err := m.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("Error %v accessing %v path", resp.StatusCode, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetVersion() (string, error) {
|
||||||
|
resp, err := m.SendRequest("/version")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(resp[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetSelfHost() (Host, error) {
|
||||||
|
resp, err := m.SendRequest("/self/host")
|
||||||
|
var host Host
|
||||||
|
if err != nil {
|
||||||
|
return host, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &host); err != nil {
|
||||||
|
return host, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return host, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetSelfContainer() (Container, error) {
|
||||||
|
resp, err := m.SendRequest("/self/container")
|
||||||
|
var container Container
|
||||||
|
if err != nil {
|
||||||
|
return container, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &container); err != nil {
|
||||||
|
return container, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return container, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetSelfServiceByName(name string) (Service, error) {
|
||||||
|
resp, err := m.SendRequest("/self/stack/services/" + name)
|
||||||
|
var service Service
|
||||||
|
if err != nil {
|
||||||
|
return service, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &service); err != nil {
|
||||||
|
return service, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetSelfService() (Service, error) {
|
||||||
|
resp, err := m.SendRequest("/self/service")
|
||||||
|
var service Service
|
||||||
|
if err != nil {
|
||||||
|
return service, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &service); err != nil {
|
||||||
|
return service, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetSelfStack() (Stack, error) {
|
||||||
|
resp, err := m.SendRequest("/self/stack")
|
||||||
|
var stack Stack
|
||||||
|
if err != nil {
|
||||||
|
return stack, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &stack); err != nil {
|
||||||
|
return stack, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stack, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetServices() ([]Service, error) {
|
||||||
|
resp, err := m.SendRequest("/services")
|
||||||
|
var services []Service
|
||||||
|
if err != nil {
|
||||||
|
return services, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &services); err != nil {
|
||||||
|
return services, err
|
||||||
|
}
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetStacks() ([]Stack, error) {
|
||||||
|
resp, err := m.SendRequest("/stacks")
|
||||||
|
var stacks []Stack
|
||||||
|
if err != nil {
|
||||||
|
return stacks, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &stacks); err != nil {
|
||||||
|
return stacks, err
|
||||||
|
}
|
||||||
|
return stacks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetContainers() ([]Container, error) {
|
||||||
|
resp, err := m.SendRequest("/containers")
|
||||||
|
var containers []Container
|
||||||
|
if err != nil {
|
||||||
|
return containers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &containers); err != nil {
|
||||||
|
return containers, err
|
||||||
|
}
|
||||||
|
return containers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetServiceContainers(serviceName string, stackName string) ([]Container, error) {
|
||||||
|
var serviceContainers = []Container{}
|
||||||
|
containers, err := m.GetContainers()
|
||||||
|
if err != nil {
|
||||||
|
return serviceContainers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
if container.StackName == stackName && container.ServiceName == serviceName {
|
||||||
|
serviceContainers = append(serviceContainers, container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceContainers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetHosts() ([]Host, error) {
|
||||||
|
resp, err := m.SendRequest("/hosts")
|
||||||
|
var hosts []Host
|
||||||
|
if err != nil {
|
||||||
|
return hosts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &hosts); err != nil {
|
||||||
|
return hosts, err
|
||||||
|
}
|
||||||
|
return hosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetHost(UUID string) (Host, error) {
|
||||||
|
var host Host
|
||||||
|
hosts, err := m.GetHosts()
|
||||||
|
if err != nil {
|
||||||
|
return host, err
|
||||||
|
}
|
||||||
|
for _, host := range hosts {
|
||||||
|
if host.UUID == UUID {
|
||||||
|
return host, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return host, fmt.Errorf("could not find host by UUID %v", UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *client) GetNetworks() ([]Network, error) {
|
||||||
|
resp, err := m.SendRequest("/networks")
|
||||||
|
var networks []Network
|
||||||
|
if err != nil {
|
||||||
|
return networks, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(resp, &networks); err != nil {
|
||||||
|
return networks, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return networks, nil
|
||||||
|
}
|
149
vendor/github.com/rancher/go-rancher-metadata/metadata/types.go
generated
vendored
Normal file
149
vendor/github.com/rancher/go-rancher-metadata/metadata/types.go
generated
vendored
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
type Stack struct {
|
||||||
|
EnvironmentName string `json:"environment_name"`
|
||||||
|
EnvironmentUUID string `json:"environment_uuid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Services []Service `json:"services"`
|
||||||
|
System bool `json:"system"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HealthCheck struct {
|
||||||
|
HealthyThreshold int `json:"healthy_threshold"`
|
||||||
|
Interval int `json:"interval"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
RequestLine string `json:"request_line"`
|
||||||
|
ResponseTimeout int `json:"response_timeout"`
|
||||||
|
UnhealthyThreshold int `json:"unhealthy_threshold"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Scale int `json:"scale"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
StackName string `json:"stack_name"`
|
||||||
|
StackUUID string `json:"stack_uuid"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
Vip string `json:"vip"`
|
||||||
|
CreateIndex int `json:"create_index"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
ExternalIps []string `json:"external_ips"`
|
||||||
|
Sidekicks []string `json:"sidekicks"`
|
||||||
|
Containers []Container `json:"containers"`
|
||||||
|
Ports []string `json:"ports"`
|
||||||
|
Labels map[string]string `json:"labels"`
|
||||||
|
Links map[string]string `json:"links"`
|
||||||
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Fqdn string `json:"fqdn"`
|
||||||
|
HealthCheck HealthCheck `json:"health_check"`
|
||||||
|
PrimaryServiceName string `json:"primary_service_name"`
|
||||||
|
LBConfig LBConfig `json:"lb_config"`
|
||||||
|
EnvironmentUUID string `json:"environment_uuid"`
|
||||||
|
State string `json:"state"`
|
||||||
|
System bool `json:"system"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
PrimaryIp string `json:"primary_ip"`
|
||||||
|
PrimaryMacAddress string `json:"primary_mac_address"`
|
||||||
|
Ips []string `json:"ips"`
|
||||||
|
Ports []string `json:"ports"`
|
||||||
|
ServiceName string `json:"service_name"`
|
||||||
|
ServiceIndex string `json:"service_index"`
|
||||||
|
StackName string `json:"stack_name"`
|
||||||
|
Labels map[string]string `json:"labels"`
|
||||||
|
CreateIndex int `json:"create_index"`
|
||||||
|
HostUUID string `json:"host_uuid"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
State string `json:"state"`
|
||||||
|
HealthState string `json:"health_state"`
|
||||||
|
ExternalId string `json:"external_id"`
|
||||||
|
StartCount int `json:"start_count"`
|
||||||
|
MemoryReservation int64 `json:"memory_reservation"`
|
||||||
|
MilliCPUReservation int64 `json:"milli_cpu_reservation"`
|
||||||
|
Dns []string `json:"dns"`
|
||||||
|
DnsSearch []string `json:"dns_search"`
|
||||||
|
HealthCheckHosts []string `json:"health_check_hosts"`
|
||||||
|
NetworkFromContainerUUID string `json:"network_from_container_uuid"`
|
||||||
|
NetworkUUID string `json:"network_uuid"`
|
||||||
|
Links map[string]string `json:"links"`
|
||||||
|
System bool `json:"system"`
|
||||||
|
EnvironmentUUID string `json:"environment_uuid"`
|
||||||
|
HealthCheck HealthCheck `json:"health_check"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Network struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
EnvironmentUUID string `json:"environment_uuid"`
|
||||||
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
|
HostPorts bool `json:"host_ports"`
|
||||||
|
Default bool `json:"is_default"`
|
||||||
|
Policy []NetworkPolicyRule `json:"policy,omitempty"`
|
||||||
|
DefaultPolicyAction string `json:"default_policy_action"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Host struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
AgentIP string `json:"agent_ip"`
|
||||||
|
HostId int `json:"host_id"`
|
||||||
|
Labels map[string]string `json:"labels"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
Memory int64 `json:"memory"`
|
||||||
|
MilliCPU int64 `json:"milli_cpu"`
|
||||||
|
LocalStorageMb int64 `json:"local_storage_mb"`
|
||||||
|
EnvironmentUUID string `json:"environment_uuid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortRule struct {
|
||||||
|
SourcePort int `json:"source_port"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
Service string `json:"service"`
|
||||||
|
TargetPort int `json:"target_port"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
BackendName string `json:"backend_name"`
|
||||||
|
Selector string `json:"selector"`
|
||||||
|
Container string `json:"container"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LBConfig struct {
|
||||||
|
Certs []string `json:"certs"`
|
||||||
|
DefaultCert string `json:"default_cert"`
|
||||||
|
PortRules []PortRule `json:"port_rules"`
|
||||||
|
Config string `json:"config"`
|
||||||
|
StickinessPolicy LBStickinessPolicy `json:"stickiness_policy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LBStickinessPolicy struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Cookie string `json:"cookie"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Indirect bool `json:"indirect"`
|
||||||
|
Nocache bool `json:"nocache"`
|
||||||
|
Postonly bool `json:"postonly"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkPolicyRuleBetween struct {
|
||||||
|
Selector string `yaml:"selector,omitempty"`
|
||||||
|
GroupBy string `yaml:"groupBy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkPolicyRuleMember struct {
|
||||||
|
Selector string `yaml:"selector,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkPolicyRule struct {
|
||||||
|
From *NetworkPolicyRuleMember `yaml:"from"`
|
||||||
|
To *NetworkPolicyRuleMember `yaml:"to"`
|
||||||
|
Ports []string `yaml:"ports"`
|
||||||
|
Within string `yaml:"within"`
|
||||||
|
Between *NetworkPolicyRuleBetween `yaml:"between"`
|
||||||
|
Action string `yaml:"action"`
|
||||||
|
}
|
19
vendor/github.com/rancher/go-rancher-metadata/metadata/utils.go
generated
vendored
Normal file
19
vendor/github.com/rancher/go-rancher-metadata/metadata/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConnection(mdClient Client) error {
|
||||||
|
var err error
|
||||||
|
maxTime := 20 * time.Second
|
||||||
|
|
||||||
|
for i := 1 * time.Second; i < maxTime; i *= time.Duration(2) {
|
||||||
|
if _, err = mdClient.GetVersion(); err != nil {
|
||||||
|
time.Sleep(i)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
Loading…
Reference in a new issue