New static configuration loading system.

Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-06-17 11:48:05 +02:00 committed by Traefiker Bot
parent d18edd6f77
commit 8d7eccad5d
165 changed files with 10894 additions and 6076 deletions

View file

@ -14,7 +14,6 @@ import (
"sync"
"time"
"github.com/containous/flaeg/parse"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/rules"
@ -39,17 +38,24 @@ var (
// Configuration holds ACME configuration provided by users
type Configuration struct {
Email string `description:"Email address used for registration"`
Email string `description:"Email address used for registration."`
ACMELogging bool `description:"Enable debug logging of ACME actions."`
CAServer string `description:"CA server to use."`
Storage string `description:"Storage to use."`
EntryPoint string `description:"EntryPoint to use."`
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. Default to 'RSA4096'"`
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge"`
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge"`
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge"`
Domains []types.Domain `description:"CN and SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='*.main.net'. Wildcard domains only accepted with DNSChallenge"`
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'."`
OnHostRule bool `description:"Enable certificate generation on router Host rules."`
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." label:"allowEmpty"`
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." label:"allowEmpty"`
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." label:"allowEmpty"`
Domains []types.Domain `description:"The list of domains for which certificates are generated on startup. Wildcard domains only accepted with DNSChallenge."`
}
// SetDefaults sets the default values.
func (a *Configuration) SetDefaults() {
a.CAServer = lego.LEDirectoryProduction
a.Storage = "acme.json"
a.KeyType = "RSA4096"
}
// Certificate is a struct which contains all data needed from an ACME certificate
@ -61,10 +67,10 @@ type Certificate struct {
// DNSChallenge contains DNS challenge Configuration
type DNSChallenge struct {
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
DelayBeforeCheck parse.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
Resolvers types.DNSResolvers `description:"Use following DNS servers to resolve the FQDN authority."`
DisablePropagationCheck bool `description:"Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]"`
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
DelayBeforeCheck types.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
Resolvers []string `description:"Use following DNS servers to resolve the FQDN authority."`
DisablePropagationCheck bool `description:"Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]"`
}
// HTTPChallenge contains HTTP challenge Configuration
@ -239,7 +245,7 @@ func (p *Provider) getClient() (*lego.Client, error) {
logger.Debug("Building ACME client...")
caServer := "https://acme-v02.api.letsencrypt.org/directory"
caServer := lego.LEDirectoryProduction
if len(p.CAServer) > 0 {
caServer = p.CAServer
}

View file

@ -4,7 +4,7 @@ import "github.com/containous/traefik/pkg/types"
// Constrainer Filter services by constraint, matching with Traefik tags.
type Constrainer struct {
Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags." export:"true"`
Constraints []*types.Constraint `description:"Filter services by constraint, matching with Traefik tags." export:"true"`
}
// MatchConstraints must match with EVERY single constraint

View file

@ -8,9 +8,9 @@ import (
"strings"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/label"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/provider/label"
"github.com/docker/go-connections/nat"
)

View file

@ -339,7 +339,7 @@ func Test_buildConfiguration(t *testing.T) {
testCases := []struct {
desc string
containers []dockerData
constraints types.Constraints
constraints []*types.Constraint
expected *config.Configuration
}{
{
@ -1924,11 +1924,11 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
constraints: types.Constraints{
&types.Constraint{
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "bar",
Value: "bar",
},
},
expected: &config.Configuration{
@ -1965,11 +1965,11 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
constraints: types.Constraints{
&types.Constraint{
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "foo",
Value: "foo",
},
},
expected: &config.Configuration{

View file

@ -45,19 +45,29 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
provider.Constrainer `mapstructure:",squash" export:"true"`
Watch bool `description:"Watch provider" export:"true"`
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
DefaultRule string `description:"Default rule"`
TLS *types.ClientTLS `description:"Enable Docker TLS support" export:"true"`
ExposedByDefault bool `description:"Expose containers by default" export:"true"`
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network" export:"true"`
SwarmMode bool `description:"Use Docker on Swarm Mode" export:"true"`
Network string `description:"Default Docker network used" export:"true"`
SwarmModeRefreshSeconds int `description:"Polling interval for swarm mode (in seconds)" export:"true"`
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
Watch bool `description:"Watch provider." export:"true"`
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint."`
DefaultRule string `description:"Default rule."`
TLS *types.ClientTLS `description:"Enable Docker TLS support." export:"true"`
ExposedByDefault bool `description:"Expose containers by default." export:"true"`
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network." export:"true"`
SwarmMode bool `description:"Use Docker on Swarm Mode." export:"true"`
Network string `description:"Default Docker network used." export:"true"`
SwarmModeRefreshSeconds types.Duration `description:"Polling interval for swarm mode." export:"true"`
defaultRuleTpl *template.Template
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Watch = true
p.ExposedByDefault = true
p.Endpoint = "unix:///var/run/docker.sock"
p.SwarmMode = false
p.SwarmModeRefreshSeconds = types.Duration(15 * time.Second)
p.DefaultRule = DefaultTemplateRule
}
// Init the provider.
func (p *Provider) Init() error {
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
@ -184,7 +194,7 @@ func (p *Provider) Provide(configurationChan chan<- config.Message, pool *safe.P
if p.SwarmMode {
errChan := make(chan error)
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
ticker := time.NewTicker(time.Second * time.Duration(p.SwarmModeRefreshSeconds))
ticker := time.NewTicker(time.Duration(p.SwarmModeRefreshSeconds))
pool.GoCtx(func(ctx context.Context) {
ctx = log.With(ctx, log.Str(log.ProviderName, "docker"))

View file

@ -3,7 +3,7 @@ package docker
import (
"fmt"
"github.com/containous/traefik/pkg/provider/label"
"github.com/containous/traefik/pkg/config/label"
)
const (

View file

@ -28,11 +28,17 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
Directory string `description:"Load configuration from one or more .toml files in a directory" export:"true"`
Watch bool `description:"Watch provider" export:"true"`
Directory string `description:"Load configuration from one or more .toml files in a directory." export:"true"`
Watch bool `description:"Watch provider." export:"true"`
Filename string `description:"Override default configuration template. For advanced users :)" export:"true"`
DebugLogGeneratedTemplate bool `description:"Enable debug logging of generated configuration template." export:"true"`
TraefikFile string
TraefikFile string `description:"-"`
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Watch = true
p.Filename = ""
}
// Init the provider

View file

@ -224,7 +224,7 @@ func createProvider(t *testing.T, test ProvideTestCase, watch bool) (*Provider,
tempDir := createTempDir(t, "testdir")
provider := &Provider{}
provider.Watch = watch
provider.Watch = true
if len(test.directoryContent) > 0 {
if !watch {

View file

@ -10,7 +10,6 @@ import (
"github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/clientset/versioned"
"github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/informers/externalversions"
"github.com/containous/traefik/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
kubeerror "k8s.io/apimachinery/pkg/api/errors"
@ -45,7 +44,7 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) {
// WatchAll starts the watch of the Provider resources and updates the stores.
// The stores can then be accessed via the Get* functions.
type Client interface {
WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error)
WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error)
GetIngressRoutes() []*v1alpha1.IngressRoute
GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP
@ -69,7 +68,7 @@ type clientWrapper struct {
labelSelector labels.Selector
isNamespaceAll bool
watchedNamespaces k8s.Namespaces
watchedNamespaces []string
}
func createClientFromConfig(c *rest.Config) (*clientWrapper, error) {
@ -144,12 +143,12 @@ func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrappe
}
// WatchAll starts namespace-specific controllers for all relevant kinds.
func (c *clientWrapper) WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
eventCh := make(chan interface{}, 1)
eventHandler := c.newResourceEventHandler(eventCh)
if len(namespaces) == 0 {
namespaces = k8s.Namespaces{metav1.NamespaceAll}
namespaces = []string{metav1.NamespaceAll}
c.isNamespaceAll = true
}
c.watchedNamespaces = namespaces

View file

@ -132,7 +132,7 @@ func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, err
return nil, false, nil
}
func (c clientMock) WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
return c.watchChan, nil
}

View file

@ -18,7 +18,6 @@ import (
"github.com/containous/traefik/pkg/job"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
"github.com/containous/traefik/pkg/safe"
"github.com/containous/traefik/pkg/tls"
corev1 "k8s.io/api/core/v1"
@ -32,13 +31,13 @@ const (
// Provider holds configurations of the provider.
type Provider struct {
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)"`
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)"`
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)"`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers" export:"true"`
Namespaces k8s.Namespaces `description:"Kubernetes namespaces" export:"true"`
LabelSelector string `description:"Kubernetes label selector to use" export:"true"`
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true"`
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)."`
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)."`
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)."`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." export:"true"`
Namespaces []string `description:"Kubernetes namespaces." export:"true"`
LabelSelector string `description:"Kubernetes label selector to use." export:"true"`
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." export:"true"`
lastConfiguration safe.Safe
}

View file

@ -7,7 +7,6 @@ import (
"time"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
kubeerror "k8s.io/apimachinery/pkg/api/errors"
@ -42,7 +41,7 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) {
// WatchAll starts the watch of the Provider resources and updates the stores.
// The stores can then be accessed via the Get* functions.
type Client interface {
WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error)
WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error)
GetIngresses() []*extensionsv1beta1.Ingress
GetService(namespace, name string) (*corev1.Service, bool, error)
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
@ -55,7 +54,7 @@ type clientWrapper struct {
factories map[string]informers.SharedInformerFactory
ingressLabelSelector labels.Selector
isNamespaceAll bool
watchedNamespaces k8s.Namespaces
watchedNamespaces []string
}
// newInClusterClient returns a new Provider client that is expected to run
@ -122,12 +121,12 @@ func newClientImpl(clientset *kubernetes.Clientset) *clientWrapper {
}
// WatchAll starts namespace-specific controllers for all relevant kinds.
func (c *clientWrapper) WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
eventCh := make(chan interface{}, 1)
eventHandler := c.newResourceEventHandler(eventCh)
if len(namespaces) == 0 {
namespaces = k8s.Namespaces{metav1.NamespaceAll}
namespaces = []string{metav1.NamespaceAll}
c.isNamespaceAll = true
}

View file

@ -99,7 +99,7 @@ func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, err
return nil, false, nil
}
func (c clientMock) WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
return c.watchChan, nil
}

View file

@ -17,7 +17,6 @@ import (
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/job"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
"github.com/containous/traefik/pkg/safe"
"github.com/containous/traefik/pkg/tls"
corev1 "k8s.io/api/core/v1"
@ -33,22 +32,22 @@ const (
// Provider holds configurations of the provider.
type Provider struct {
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)"`
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)"`
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)"`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers" export:"true"`
Namespaces k8s.Namespaces `description:"Kubernetes namespaces" export:"true"`
LabelSelector string `description:"Kubernetes Ingress label selector to use" export:"true"`
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true"`
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint"`
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)."`
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)."`
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)."`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." export:"true"`
Namespaces []string `description:"Kubernetes namespaces." export:"true"`
LabelSelector string `description:"Kubernetes Ingress label selector to use." export:"true"`
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." export:"true"`
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint."`
lastConfiguration safe.Safe
}
// EndpointIngress holds the endpoint information for the Kubernetes provider
type EndpointIngress struct {
IP string `description:"IP used for Kubernetes Ingress endpoints"`
Hostname string `description:"Hostname used for Kubernetes Ingress endpoints"`
PublishedService string `description:"Published Kubernetes Service to copy status from"`
IP string `description:"IP used for Kubernetes Ingress endpoints."`
Hostname string `description:"Hostname used for Kubernetes Ingress endpoints."`
PublishedService string `description:"Published Kubernetes Service to copy status from."`
}
func (p *Provider) newK8sClient(ctx context.Context, ingressLabelSelector string) (*clientWrapper, error) {

View file

@ -1,32 +0,0 @@
package k8s
import (
"fmt"
"strings"
)
// 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 *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 = val.(Namespaces)
}

View file

@ -1,306 +0,0 @@
package internal
import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/containous/flaeg/parse"
)
type initializer interface {
SetDefaults()
}
// Fill the fields of the element.
// nodes -> element
func Fill(element interface{}, node *Node) error {
if element == nil || node == nil {
return nil
}
if node.Kind == 0 {
return fmt.Errorf("missing node type: %s", node.Name)
}
elem := reflect.ValueOf(element)
if elem.Kind() == reflect.Struct {
return fmt.Errorf("struct are not supported, use pointer instead")
}
return fill(elem.Elem(), node)
}
func fill(field reflect.Value, node *Node) error {
// related to allow-empty tag
if node.Disabled {
return nil
}
switch field.Kind() {
case reflect.String:
field.SetString(node.Value)
return nil
case reflect.Bool:
val, err := strconv.ParseBool(node.Value)
if err != nil {
return err
}
field.SetBool(val)
return nil
case reflect.Int8:
return setInt(field, node.Value, 8)
case reflect.Int16:
return setInt(field, node.Value, 16)
case reflect.Int32:
return setInt(field, node.Value, 32)
case reflect.Int64, reflect.Int:
return setInt(field, node.Value, 64)
case reflect.Uint8:
return setUint(field, node.Value, 8)
case reflect.Uint16:
return setUint(field, node.Value, 16)
case reflect.Uint32:
return setUint(field, node.Value, 32)
case reflect.Uint64, reflect.Uint:
return setUint(field, node.Value, 64)
case reflect.Float32:
return setFloat(field, node.Value, 32)
case reflect.Float64:
return setFloat(field, node.Value, 64)
case reflect.Struct:
return setStruct(field, node)
case reflect.Ptr:
return setPtr(field, node)
case reflect.Map:
return setMap(field, node)
case reflect.Slice:
return setSlice(field, node)
default:
return nil
}
}
func setPtr(field reflect.Value, node *Node) error {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
if field.Type().Implements(reflect.TypeOf((*initializer)(nil)).Elem()) {
method := field.MethodByName("SetDefaults")
if method.IsValid() {
method.Call([]reflect.Value{})
}
}
}
return fill(field.Elem(), node)
}
func setStruct(field reflect.Value, node *Node) error {
for _, child := range node.Children {
fd := field.FieldByName(child.FieldName)
zeroValue := reflect.Value{}
if fd == zeroValue {
return fmt.Errorf("field not found, node: %s (%s)", child.Name, child.FieldName)
}
err := fill(fd, child)
if err != nil {
return err
}
}
return nil
}
func setSlice(field reflect.Value, node *Node) error {
if field.Type().Elem().Kind() == reflect.Struct {
return setSliceAsStruct(field, node)
}
if len(node.Value) == 0 {
return nil
}
values := strings.Split(node.Value, ",")
slice := reflect.MakeSlice(field.Type(), len(values), len(values))
field.Set(slice)
for i := 0; i < len(values); i++ {
value := strings.TrimSpace(values[i])
switch field.Type().Elem().Kind() {
case reflect.String:
field.Index(i).Set(reflect.ValueOf(value))
case reflect.Int:
val, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
field.Index(i).SetInt(val)
case reflect.Int8:
err := setInt(field.Index(i), value, 8)
if err != nil {
return err
}
case reflect.Int16:
err := setInt(field.Index(i), value, 16)
if err != nil {
return err
}
case reflect.Int32:
err := setInt(field.Index(i), value, 32)
if err != nil {
return err
}
case reflect.Int64:
err := setInt(field.Index(i), value, 64)
if err != nil {
return err
}
case reflect.Uint:
val, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
field.Index(i).SetUint(val)
case reflect.Uint8:
err := setUint(field.Index(i), value, 8)
if err != nil {
return err
}
case reflect.Uint16:
err := setUint(field.Index(i), value, 16)
if err != nil {
return err
}
case reflect.Uint32:
err := setUint(field.Index(i), value, 32)
if err != nil {
return err
}
case reflect.Uint64:
err := setUint(field.Index(i), value, 64)
if err != nil {
return err
}
case reflect.Float32:
err := setFloat(field.Index(i), value, 32)
if err != nil {
return err
}
case reflect.Float64:
err := setFloat(field.Index(i), value, 64)
if err != nil {
return err
}
case reflect.Bool:
val, err := strconv.ParseBool(value)
if err != nil {
return err
}
field.Index(i).SetBool(val)
default:
return fmt.Errorf("unsupported type: %s", field.Type().Elem())
}
}
return nil
}
func setSliceAsStruct(field reflect.Value, node *Node) error {
if len(node.Children) == 0 {
return fmt.Errorf("invalid slice: node %s", node.Name)
}
// use Ptr to allow "SetDefaults"
value := reflect.New(reflect.PtrTo(field.Type().Elem()))
err := setPtr(value, node)
if err != nil {
return err
}
elem := value.Elem().Elem()
field.Set(reflect.MakeSlice(field.Type(), 1, 1))
field.Index(0).Set(elem)
return nil
}
func setMap(field reflect.Value, node *Node) error {
if field.IsNil() {
field.Set(reflect.MakeMap(field.Type()))
}
for _, child := range node.Children {
ptrValue := reflect.New(reflect.PtrTo(field.Type().Elem()))
err := fill(ptrValue, child)
if err != nil {
return err
}
value := ptrValue.Elem().Elem()
key := reflect.ValueOf(child.Name)
field.SetMapIndex(key, value)
}
return nil
}
func setInt(field reflect.Value, value string, bitSize int) error {
switch field.Type() {
case reflect.TypeOf(parse.Duration(0)):
return setDuration(field, value, bitSize, time.Second)
case reflect.TypeOf(time.Duration(0)):
return setDuration(field, value, bitSize, time.Nanosecond)
default:
val, err := strconv.ParseInt(value, 10, bitSize)
if err != nil {
return err
}
field.Set(reflect.ValueOf(val).Convert(field.Type()))
return nil
}
}
func setDuration(field reflect.Value, value string, bitSize int, defaultUnit time.Duration) error {
val, err := strconv.ParseInt(value, 10, bitSize)
if err == nil {
field.Set(reflect.ValueOf(time.Duration(val) * defaultUnit).Convert(field.Type()))
return nil
}
duration, err := time.ParseDuration(value)
if err != nil {
return err
}
field.Set(reflect.ValueOf(duration).Convert(field.Type()))
return nil
}
func setUint(field reflect.Value, value string, bitSize int) error {
val, err := strconv.ParseUint(value, 10, bitSize)
if err != nil {
return err
}
field.Set(reflect.ValueOf(val).Convert(field.Type()))
return nil
}
func setFloat(field reflect.Value, value string, bitSize int) error {
val, err := strconv.ParseFloat(value, bitSize)
if err != nil {
return err
}
field.Set(reflect.ValueOf(val).Convert(field.Type()))
return nil
}

File diff suppressed because it is too large Load diff

View file

@ -1,163 +0,0 @@
package internal
import (
"fmt"
"reflect"
"strconv"
"strings"
)
// EncodeToNode Converts an element to a node.
// element -> nodes
func EncodeToNode(element interface{}) (*Node, error) {
rValue := reflect.ValueOf(element)
node := &Node{Name: "traefik"}
err := setNodeValue(node, rValue)
if err != nil {
return nil, err
}
return node, nil
}
func setNodeValue(node *Node, rValue reflect.Value) error {
switch rValue.Kind() {
case reflect.String:
node.Value = rValue.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
node.Value = strconv.FormatInt(rValue.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
node.Value = strconv.FormatUint(rValue.Uint(), 10)
case reflect.Float32, reflect.Float64:
node.Value = strconv.FormatFloat(rValue.Float(), 'f', 6, 64)
case reflect.Bool:
node.Value = strconv.FormatBool(rValue.Bool())
case reflect.Struct:
return setStructValue(node, rValue)
case reflect.Ptr:
return setNodeValue(node, rValue.Elem())
case reflect.Map:
return setMapValue(node, rValue)
case reflect.Slice:
return setSliceValue(node, rValue)
default:
// noop
}
return nil
}
func setStructValue(node *Node, rValue reflect.Value) error {
rType := rValue.Type()
for i := 0; i < rValue.NumField(); i++ {
field := rType.Field(i)
fieldValue := rValue.Field(i)
if !isExported(field) {
continue
}
if field.Tag.Get(TagLabel) == "-" {
continue
}
if err := isSupportedType(field); err != nil {
return err
}
if isSkippedField(field, fieldValue) {
continue
}
nodeName := field.Name
if field.Type.Kind() == reflect.Slice && len(field.Tag.Get(TagLabelSliceAsStruct)) != 0 {
nodeName = field.Tag.Get(TagLabelSliceAsStruct)
}
child := &Node{Name: nodeName, FieldName: field.Name}
if err := setNodeValue(child, fieldValue); err != nil {
return err
}
if field.Type.Kind() == reflect.Ptr && len(child.Children) == 0 {
if field.Tag.Get(TagLabel) != "allowEmpty" {
continue
}
child.Value = "true"
}
node.Children = append(node.Children, child)
}
return nil
}
func setMapValue(node *Node, rValue reflect.Value) error {
for _, key := range rValue.MapKeys() {
child := &Node{Name: key.String(), FieldName: key.String()}
node.Children = append(node.Children, child)
if err := setNodeValue(child, rValue.MapIndex(key)); err != nil {
return err
}
}
return nil
}
func setSliceValue(node *Node, rValue reflect.Value) error {
// label-slice-as-struct
if rValue.Type().Elem().Kind() == reflect.Struct && !strings.EqualFold(node.Name, node.FieldName) {
if rValue.Len() > 1 {
return fmt.Errorf("node %s has too many slice entries: %d", node.Name, rValue.Len())
}
if err := setNodeValue(node, rValue.Index(0)); err != nil {
return err
}
}
var values []string
for i := 0; i < rValue.Len(); i++ {
eValue := rValue.Index(i)
switch eValue.Kind() {
case reflect.String:
values = append(values, eValue.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
values = append(values, strconv.FormatInt(eValue.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
values = append(values, strconv.FormatUint(eValue.Uint(), 10))
case reflect.Float32, reflect.Float64:
values = append(values, strconv.FormatFloat(eValue.Float(), 'f', 6, 64))
case reflect.Bool:
values = append(values, strconv.FormatBool(eValue.Bool()))
default:
// noop
}
}
node.Value = strings.Join(values, ", ")
return nil
}
func isSkippedField(field reflect.StructField, fieldValue reflect.Value) bool {
if field.Type.Kind() == reflect.String && fieldValue.Len() == 0 {
return true
}
if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct && fieldValue.IsNil() {
return true
}
if (field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Map) &&
(fieldValue.IsNil() || fieldValue.Len() == 0) {
return true
}
return false
}

View file

@ -1,593 +0,0 @@
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEncodeToNode(t *testing.T) {
type expected struct {
node *Node
error bool
}
testCases := []struct {
desc string
element interface{}
expected expected
}{
{
desc: "string",
element: struct {
Foo string
}{Foo: "bar"},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "bar"},
}},
},
},
{
desc: "2 string fields",
element: struct {
Foo string
Fii string
}{Foo: "bar", Fii: "hii"},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "bar"},
{Name: "Fii", FieldName: "Fii", Value: "hii"},
}},
},
},
{
desc: "int",
element: struct {
Foo int
}{Foo: 1},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "1"},
}},
},
},
{
desc: "int8",
element: struct {
Foo int8
}{Foo: 2},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "2"},
}},
},
},
{
desc: "int16",
element: struct {
Foo int16
}{Foo: 2},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "2"},
}},
},
},
{
desc: "int32",
element: struct {
Foo int32
}{Foo: 2},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "2"},
}},
},
},
{
desc: "int64",
element: struct {
Foo int64
}{Foo: 2},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "2"},
}},
},
},
{
desc: "uint",
element: struct {
Foo uint
}{Foo: 1},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "1"},
}},
},
},
{
desc: "uint8",
element: struct {
Foo uint8
}{Foo: 2},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "2"},
}},
},
},
{
desc: "uint16",
element: struct {
Foo uint16
}{Foo: 2},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "2"},
}},
},
},
{
desc: "uint32",
element: struct {
Foo uint32
}{Foo: 2},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "2"},
}},
},
},
{
desc: "uint64",
element: struct {
Foo uint64
}{Foo: 2},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "2"},
}},
},
},
{
desc: "float32",
element: struct {
Foo float32
}{Foo: 1.12},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "1.120000"},
}},
},
},
{
desc: "float64",
element: struct {
Foo float64
}{Foo: 1.12},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "1.120000"},
}},
},
},
{
desc: "bool",
element: struct {
Foo bool
}{Foo: true},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "true"},
}},
},
},
{
desc: "struct",
element: struct {
Foo struct {
Fii string
Fuu string
}
}{
Foo: struct {
Fii string
Fuu string
}{
Fii: "hii",
Fuu: "huu",
},
},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "Fii", FieldName: "Fii", Value: "hii"},
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
}},
}},
},
},
{
desc: "struct unexported field",
element: struct {
Foo struct {
Fii string
fuu string
}
}{
Foo: struct {
Fii string
fuu string
}{
Fii: "hii",
fuu: "huu",
},
},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "Fii", FieldName: "Fii", Value: "hii"},
}},
}},
},
},
{
desc: "struct pointer",
element: struct {
Foo *struct {
Fii string
Fuu string
}
}{
Foo: &struct {
Fii string
Fuu string
}{
Fii: "hii",
Fuu: "huu",
},
},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "Fii", FieldName: "Fii", Value: "hii"},
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
}},
}},
},
},
{
desc: "string pointer",
element: struct {
Foo *struct {
Fii *string
Fuu string
}
}{
Foo: &struct {
Fii *string
Fuu string
}{
Fii: func(v string) *string { return &v }("hii"),
Fuu: "huu",
},
},
expected: expected{error: true},
},
{
desc: "struct nil pointer",
element: struct {
Foo *struct {
Fii *string
Fuu string
}
}{
Foo: &struct {
Fii *string
Fuu string
}{
Fuu: "huu",
},
},
expected: expected{error: true},
},
{
desc: "struct nil struct pointer",
element: struct {
Foo *struct {
Fii *string
Fuu string
}
}{
Foo: nil,
},
expected: expected{node: &Node{Name: "traefik"}},
},
{
desc: "struct pointer, not allowEmpty",
element: struct {
Foo *struct {
Fii string
Fuu string
}
}{
Foo: &struct {
Fii string
Fuu string
}{},
},
expected: expected{node: &Node{Name: "traefik"}},
},
{
desc: "struct pointer, allowEmpty",
element: struct {
Foo *struct {
Fii string
Fuu string
} `label:"allowEmpty"`
}{
Foo: &struct {
Fii string
Fuu string
}{},
},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "true"},
}},
},
},
{
desc: "map",
element: struct {
Foo struct {
Bar map[string]string
}
}{
Foo: struct {
Bar map[string]string
}{
Bar: map[string]string{
"name1": "huu",
},
},
},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Children: []*Node{
{Name: "name1", FieldName: "name1", Value: "huu"},
}},
}},
}}},
},
{
desc: "empty map",
element: struct {
Bar map[string]string
}{
Bar: map[string]string{},
},
expected: expected{node: &Node{Name: "traefik"}},
},
{
desc: "map nil",
element: struct {
Bar map[string]string
}{
Bar: nil,
},
expected: expected{node: &Node{Name: "traefik"}},
},
{
desc: "map with non string key",
element: struct {
Foo struct {
Bar map[int]string
}
}{
Foo: struct {
Bar map[int]string
}{
Bar: map[int]string{
1: "huu",
},
},
},
expected: expected{error: true},
},
{
desc: "slice of string",
element: struct{ Bar []string }{Bar: []string{"huu", "hii"}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "huu, hii"},
}},
},
},
{
desc: "slice of int",
element: struct{ Bar []int }{Bar: []int{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of int8",
element: struct{ Bar []int8 }{Bar: []int8{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of int16",
element: struct{ Bar []int16 }{Bar: []int16{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of int32",
element: struct{ Bar []int32 }{Bar: []int32{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of int64",
element: struct{ Bar []int64 }{Bar: []int64{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of uint",
element: struct{ Bar []uint }{Bar: []uint{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of uint8",
element: struct{ Bar []uint8 }{Bar: []uint8{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of uint16",
element: struct{ Bar []uint16 }{Bar: []uint16{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of uint32",
element: struct{ Bar []uint32 }{Bar: []uint32{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of uint64",
element: struct{ Bar []uint64 }{Bar: []uint64{4, 2, 3}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
}},
},
},
{
desc: "slice of float32",
element: struct{ Bar []float32 }{Bar: []float32{4.1, 2, 3.2}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4.100000, 2.000000, 3.200000"},
}},
},
},
{
desc: "slice of float64",
element: struct{ Bar []float64 }{Bar: []float64{4.1, 2, 3.2}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "4.100000, 2.000000, 3.200000"},
}},
},
},
{
desc: "slice of bool",
element: struct{ Bar []bool }{Bar: []bool{true, false, true}},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "true, false, true"},
}},
},
},
{
desc: "slice label-slice-as-struct",
element: &struct {
Foo []struct {
Bar string
Bir string
} `label-slice-as-struct:"Fii"`
}{
Foo: []struct {
Bar string
Bir string
}{
{
Bar: "haa",
Bir: "hii",
},
},
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{{
Name: "Fii",
FieldName: "Foo",
Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "haa"},
{Name: "Bir", FieldName: "Bir", Value: "hii"},
},
}},
}},
},
{
desc: "slice label-slice-as-struct several slice entries",
element: &struct {
Foo []struct {
Bar string
Bir string
} `label-slice-as-struct:"Fii"`
}{
Foo: []struct {
Bar string
Bir string
}{
{
Bar: "haa",
Bir: "hii",
},
{
Bar: "haa",
Bir: "hii",
},
},
},
expected: expected{error: true},
},
{
desc: "empty slice",
element: struct {
Bar []string
}{
Bar: []string{},
},
expected: expected{node: &Node{Name: "traefik"}},
},
{
desc: "nil slice",
element: struct {
Bar []string
}{
Bar: nil,
},
expected: expected{node: &Node{Name: "traefik"}},
},
{
desc: "ignore slice",
element: struct {
Bar []string `label:"-"`
}{
Bar: []string{"huu", "hii"},
},
expected: expected{node: &Node{Name: "traefik"}},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
node, err := EncodeToNode(test.element)
if test.expected.error {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, test.expected.node, node)
}
})
}
}

View file

@ -1,78 +0,0 @@
package internal
import (
"fmt"
"sort"
"strings"
)
// DecodeToNode Converts the labels to a node.
// labels -> nodes
func DecodeToNode(labels map[string]string, filters ...string) (*Node, error) {
var sortedKeys []string
for key := range labels {
if len(filters) == 0 {
sortedKeys = append(sortedKeys, key)
continue
}
for _, filter := range filters {
if len(key) >= len(filter) && strings.EqualFold(key[:len(filter)], filter) {
sortedKeys = append(sortedKeys, key)
continue
}
}
}
sort.Strings(sortedKeys)
labelRoot := "traefik"
var node *Node
for i, key := range sortedKeys {
split := strings.Split(key, ".")
if split[0] != labelRoot {
// TODO (@ldez): error or continue
return nil, fmt.Errorf("invalid label root %s", split[0])
}
labelRoot = split[0]
if i == 0 {
node = &Node{}
}
decodeToNode(node, split, labels[key])
}
return node, nil
}
func decodeToNode(root *Node, path []string, value string) {
if len(root.Name) == 0 {
root.Name = path[0]
}
// it's a leaf or not -> children
if len(path) > 1 {
if n := containsNode(root.Children, path[1]); n != nil {
// the child already exists
decodeToNode(n, path[1:], value)
} else {
// new child
child := &Node{Name: path[1]}
decodeToNode(child, path[1:], value)
root.Children = append(root.Children, child)
}
} else {
root.Value = value
}
}
func containsNode(nodes []*Node, name string) *Node {
for _, n := range nodes {
if name == n.Name {
return n
}
}
return nil
}

View file

@ -1,202 +0,0 @@
package internal
import (
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecodeToNode(t *testing.T) {
type expected struct {
error bool
node *Node
}
testCases := []struct {
desc string
in map[string]string
filters []string
expected expected
}{
{
desc: "no label",
in: map[string]string{},
expected: expected{node: nil},
},
{
desc: "level 1",
in: map[string]string{
"traefik.foo": "bar",
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Value: "bar"},
},
}},
},
{
desc: "level 1 empty value",
in: map[string]string{
"traefik.foo": "",
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Value: ""},
},
}},
},
{
desc: "level 2",
in: map[string]string{
"traefik.foo.bar": "bar",
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{{
Name: "foo",
Children: []*Node{
{Name: "bar", Value: "bar"},
},
}},
}},
},
{
desc: "several entries, level 0",
in: map[string]string{
"traefik": "bar",
"traefic": "bur",
},
expected: expected{error: true},
},
{
desc: "several entries, prefix filter",
in: map[string]string{
"traefik.foo": "bar",
"traefik.fii": "bir",
},
filters: []string{"traefik.Foo"},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Value: "bar"},
},
}},
},
{
desc: "several entries, level 1",
in: map[string]string{
"traefik.foo": "bar",
"traefik.fii": "bur",
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "fii", Value: "bur"},
{Name: "foo", Value: "bar"},
},
}},
},
{
desc: "several entries, level 2",
in: map[string]string{
"traefik.foo.aaa": "bar",
"traefik.foo.bbb": "bur",
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Children: []*Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
}},
},
}},
},
{
desc: "several entries, level 2, 3 children",
in: map[string]string{
"traefik.foo.aaa": "bar",
"traefik.foo.bbb": "bur",
"traefik.foo.ccc": "bir",
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Children: []*Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
{Name: "ccc", Value: "bir"},
}},
},
}},
},
{
desc: "several entries, level 3",
in: map[string]string{
"traefik.foo.bar.aaa": "bar",
"traefik.foo.bar.bbb": "bur",
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Children: []*Node{
{Name: "bar", Children: []*Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
}},
}},
},
}},
},
{
desc: "several entries, level 3, 2 children level 1",
in: map[string]string{
"traefik.foo.bar.aaa": "bar",
"traefik.foo.bar.bbb": "bur",
"traefik.bar.foo.bbb": "bir",
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "bar", Children: []*Node{
{Name: "foo", Children: []*Node{
{Name: "bbb", Value: "bir"},
}},
}},
{Name: "foo", Children: []*Node{
{Name: "bar", Children: []*Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
}},
}},
},
}},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
out, err := DecodeToNode(test.in, test.filters...)
if test.expected.error {
require.Error(t, err)
} else {
require.NoError(t, err)
if !assert.Equal(t, test.expected.node, out) {
bytes, err := json.MarshalIndent(out, "", " ")
require.NoError(t, err)
fmt.Println(string(bytes))
}
}
})
}
}

View file

@ -1,24 +0,0 @@
package internal
// EncodeNode Converts a node to labels.
// nodes -> labels
func EncodeNode(node *Node) map[string]string {
labels := make(map[string]string)
encodeNode(labels, node.Name, node)
return labels
}
func encodeNode(labels map[string]string, root string, node *Node) {
for _, child := range node.Children {
if child.Disabled {
continue
}
childName := root + "." + child.Name
if len(child.Children) > 0 {
encodeNode(labels, childName, child)
} else if len(child.Name) > 0 {
labels[childName] = child.Value
}
}
}

View file

@ -1,156 +0,0 @@
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEncodeNode(t *testing.T) {
testCases := []struct {
desc string
node *Node
expected map[string]string
}{
{
desc: "1 label",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "aaa", Value: "bar"},
},
},
expected: map[string]string{
"traefik.aaa": "bar",
},
},
{
desc: "2 labels",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
},
},
expected: map[string]string{
"traefik.aaa": "bar",
"traefik.bbb": "bur",
},
},
{
desc: "2 labels, 1 disabled",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur", Disabled: true},
},
},
expected: map[string]string{
"traefik.aaa": "bar",
},
},
{
desc: "2 levels",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Children: []*Node{
{Name: "aaa", Value: "bar"},
}},
},
},
expected: map[string]string{
"traefik.foo.aaa": "bar",
},
},
{
desc: "3 levels",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Children: []*Node{
{Name: "bar", Children: []*Node{
{Name: "aaa", Value: "bar"},
}},
}},
},
},
expected: map[string]string{
"traefik.foo.bar.aaa": "bar",
},
},
{
desc: "2 levels, same root",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Children: []*Node{
{Name: "bar", Children: []*Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
}},
}},
},
},
expected: map[string]string{
"traefik.foo.bar.aaa": "bar",
"traefik.foo.bar.bbb": "bur",
},
},
{
desc: "several levels, different root",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "bar", Children: []*Node{
{Name: "ccc", Value: "bir"},
}},
{Name: "foo", Children: []*Node{
{Name: "bar", Children: []*Node{
{Name: "aaa", Value: "bar"},
}},
}},
},
},
expected: map[string]string{
"traefik.foo.bar.aaa": "bar",
"traefik.bar.ccc": "bir",
},
},
{
desc: "multiple labels, multiple levels",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "bar", Children: []*Node{
{Name: "ccc", Value: "bir"},
}},
{Name: "foo", Children: []*Node{
{Name: "bar", Children: []*Node{
{Name: "aaa", Value: "bar"},
{Name: "bbb", Value: "bur"},
}},
}},
},
},
expected: map[string]string{
"traefik.foo.bar.aaa": "bar",
"traefik.foo.bar.bbb": "bur",
"traefik.bar.ccc": "bir",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
labels := EncodeNode(test.node)
assert.Equal(t, test.expected, labels)
})
}
}

View file

@ -1,13 +0,0 @@
package internal
import "reflect"
// Node a label node.
type Node struct {
Name string `json:"name"`
FieldName string `json:"fieldName"`
Value string `json:"value,omitempty"`
Disabled bool `json:"disabled,omitempty"`
Kind reflect.Kind `json:"kind,omitempty"`
Children []*Node `json:"children,omitempty"`
}

View file

@ -1,168 +0,0 @@
package internal
import (
"errors"
"fmt"
"reflect"
"strings"
)
// AddMetadata Adds metadata to a node.
// nodes + element -> nodes
func AddMetadata(structure interface{}, node *Node) error {
if node == nil {
return nil
}
if len(node.Children) == 0 {
return fmt.Errorf("invalid node %s: no child", node.Name)
}
if structure == nil {
return errors.New("nil structure")
}
rootType := reflect.TypeOf(structure)
node.Kind = rootType.Kind()
return browseChildren(rootType, node)
}
func addMetadata(rootType reflect.Type, node *Node) error {
rType := rootType
if rootType.Kind() == reflect.Ptr {
rType = rootType.Elem()
}
field, err := findTypedField(rType, node)
if err != nil {
return err
}
if err = isSupportedType(field); err != nil {
return err
}
fType := field.Type
node.Kind = fType.Kind()
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct ||
fType.Kind() == reflect.Map {
if len(node.Children) == 0 && field.Tag.Get(TagLabel) != "allowEmpty" {
return fmt.Errorf("node %s (type %s) must have children", node.Name, fType)
}
node.Disabled = len(node.Value) > 0 && !strings.EqualFold(node.Value, "true") && field.Tag.Get(TagLabel) == "allowEmpty"
}
if len(node.Children) == 0 {
return nil
}
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct {
return browseChildren(fType, node)
}
if fType.Kind() == reflect.Map {
for _, child := range node.Children {
// elem is a map entry value type
elem := fType.Elem()
child.Kind = elem.Kind()
if elem.Kind() == reflect.Map || elem.Kind() == reflect.Struct ||
(elem.Kind() == reflect.Ptr && elem.Elem().Kind() == reflect.Struct) {
if err = browseChildren(elem, child); err != nil {
return err
}
}
}
return nil
}
// only for struct/Ptr with label-slice-as-struct tag
if fType.Kind() == reflect.Slice {
return browseChildren(fType.Elem(), node)
}
return fmt.Errorf("invalid node %s: %v", node.Name, fType.Kind())
}
func findTypedField(rType reflect.Type, node *Node) (reflect.StructField, error) {
for i := 0; i < rType.NumField(); i++ {
cField := rType.Field(i)
fieldName := cField.Tag.Get(TagLabelSliceAsStruct)
if len(fieldName) == 0 {
fieldName = cField.Name
}
if isExported(cField) && strings.EqualFold(fieldName, node.Name) {
node.FieldName = cField.Name
return cField, nil
}
}
return reflect.StructField{}, fmt.Errorf("field not found, node: %s", node.Name)
}
func browseChildren(fType reflect.Type, node *Node) error {
for _, child := range node.Children {
if err := addMetadata(fType, child); err != nil {
return err
}
}
return nil
}
// isExported return true is a struct field is exported, else false
// https://golang.org/pkg/reflect/#StructField
func isExported(f reflect.StructField) bool {
if f.PkgPath != "" && !f.Anonymous {
return false
}
return true
}
func isSupportedType(field reflect.StructField) error {
fType := field.Type
if fType.Kind() == reflect.Slice {
switch fType.Elem().Kind() {
case reflect.String,
reflect.Bool,
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Uintptr,
reflect.Float32,
reflect.Float64:
return nil
default:
if len(field.Tag.Get(TagLabelSliceAsStruct)) > 0 {
return nil
}
return fmt.Errorf("unsupported slice type: %v", fType)
}
}
if fType.Kind() == reflect.Ptr && fType.Elem().Kind() != reflect.Struct {
return fmt.Errorf("unsupported pointer type: %v", fType.Elem())
}
if fType.Kind() == reflect.Map && fType.Key().Kind() != reflect.String {
return fmt.Errorf("unsupported map key type: %v", fType.Key())
}
if fType.Kind() == reflect.Func {
return fmt.Errorf("unsupported type: %v", fType)
}
return nil
}

View file

@ -1,802 +0,0 @@
package internal
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAddMetadata(t *testing.T) {
type expected struct {
node *Node
error bool
}
type interf interface{}
testCases := []struct {
desc string
tree *Node
structure interface{}
expected expected
}{
{
desc: "Node Nil",
tree: nil,
structure: nil,
expected: expected{node: nil},
},
{
desc: "Empty Node",
tree: &Node{},
structure: nil,
expected: expected{error: true},
},
{
desc: "Nil structure",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "bar"},
},
},
structure: nil,
expected: expected{error: true},
},
{
desc: "level 0",
tree: &Node{Name: "traefik", Value: "bar"},
expected: expected{error: true},
},
{
desc: "level 1",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "bar"},
},
},
structure: struct{ Foo string }{},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "bar", Kind: reflect.String},
},
},
},
},
{
desc: "level 1, pointer",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "bar"},
},
},
structure: &struct{ Foo string }{},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Ptr,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "bar", Kind: reflect.String},
},
},
},
},
{
desc: "level 1, slice",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "bar,bur"},
},
},
structure: struct{ Foo []string }{},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "bar,bur", Kind: reflect.Slice},
},
},
},
},
{
desc: "level 1, interface",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "", Children: []*Node{
{Name: "Fii", Value: "hii"},
}},
},
},
structure: struct{ Foo interf }{},
expected: expected{error: true},
},
{
desc: "level 1, slice struct",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "1,2"},
},
},
structure: struct {
Foo []struct{ Foo string }
}{},
expected: expected{error: true},
},
{
desc: "level 1, map string",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Children: []*Node{
{Name: "name1", Value: "bar"},
{Name: "name2", Value: "bur"},
}},
},
},
structure: struct{ Foo map[string]string }{},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Kind: reflect.Map, Children: []*Node{
{Name: "name1", Value: "bar", Kind: reflect.String},
{Name: "name2", Value: "bur", Kind: reflect.String},
}},
},
},
},
},
{
desc: "level 1, map struct",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Children: []*Node{
{Name: "name1", Children: []*Node{
{Name: "Fii", Value: "bar"},
}},
}},
},
},
structure: struct {
Foo map[string]struct{ Fii string }
}{},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Kind: reflect.Map, Children: []*Node{
{Name: "name1", Kind: reflect.Struct, Children: []*Node{
{Name: "Fii", FieldName: "Fii", Value: "bar", Kind: reflect.String},
}},
}},
},
},
},
},
{
desc: "level 1, map int as key",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Children: []*Node{
{Name: "name1", Children: []*Node{
{Name: "Fii", Value: "bar"},
}},
}},
},
},
structure: struct {
Foo map[int]struct{ Fii string }
}{},
expected: expected{error: true},
},
{
desc: "level 1, int pointer",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "0"},
},
},
structure: struct {
Foo *int
}{},
expected: expected{error: true},
},
{
desc: "level 1, 2 children with different types",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "bar"},
{Name: "Fii", Value: "1"},
},
},
structure: struct {
Foo string
Fii int
}{},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "bar", Kind: reflect.String},
{Name: "Fii", FieldName: "Fii", Value: "1", Kind: reflect.Int},
},
},
},
},
{
desc: "level 1, use exported instead of unexported",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Value: "bar"},
},
},
structure: struct {
foo int
Foo string
}{},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "foo", Value: "bar", FieldName: "Foo", Kind: reflect.String},
},
},
},
},
{
desc: "level 1, unexported",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Value: "bar"},
},
},
structure: struct {
foo string
}{},
expected: expected{error: true},
},
{
desc: "level 1, 3 children with different types",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "bar"},
{Name: "Fii", Value: "1"},
{Name: "Fuu", Value: "true"},
},
},
structure: struct {
Foo string
Fii int
Fuu bool
}{},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "bar", Kind: reflect.String},
{Name: "Fii", FieldName: "Fii", Value: "1", Kind: reflect.Int},
{Name: "Fuu", FieldName: "Fuu", Value: "true", Kind: reflect.Bool},
},
},
},
},
{
desc: "level 2",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Children: []*Node{
{Name: "Bar", Value: "bir"},
}},
},
},
structure: struct {
Foo struct {
Bar string
}
}{
Foo: struct {
Bar string
}{},
},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Kind: reflect.Struct, Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "bir", Kind: reflect.String},
}},
},
},
},
},
{
desc: "level 2, struct without children",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo"},
},
},
structure: struct {
Foo struct {
Bar string
}
}{
Foo: struct {
Bar string
}{},
},
expected: expected{error: true},
},
{
desc: "level 2, slice-as-struct",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Fii", Children: []*Node{
{Name: "bar", Value: "haa"},
{Name: "bir", Value: "hii"},
}},
},
},
structure: struct {
Foo []struct {
Bar string
Bir string
} `label-slice-as-struct:"Fii"`
}{
Foo: []struct {
Bar string
Bir string
}{},
},
expected: expected{node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Fii",
FieldName: "Foo",
Kind: reflect.Slice,
Children: []*Node{
{Name: "bar", FieldName: "Bar", Kind: reflect.String, Value: "haa"},
{Name: "bir", FieldName: "Bir", Kind: reflect.String, Value: "hii"},
},
},
},
}},
},
{
desc: "level 2, slice-as-struct without children",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Fii"},
},
},
structure: struct {
Foo []struct {
Bar string
Bir string
} `label-slice-as-struct:"Fii"`
}{
Foo: []struct {
Bar string
Bir string
}{},
},
expected: expected{node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Fii",
FieldName: "Foo",
Kind: reflect.Slice,
},
},
}},
},
{
desc: "level 2, struct with allowEmpty, value true",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "true"},
},
},
structure: struct {
Foo struct {
Bar string
} `label:"allowEmpty"`
}{
Foo: struct {
Bar string
}{},
},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "true", Kind: reflect.Struct},
},
},
},
},
{
desc: "level 2, struct with allowEmpty, value true with case variation",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "TruE"},
},
},
structure: struct {
Foo struct {
Bar string
} `label:"allowEmpty"`
}{
Foo: struct {
Bar string
}{},
},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "TruE", Kind: reflect.Struct},
},
},
},
},
{
desc: "level 2, struct with allowEmpty, value false",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "false"},
},
},
structure: struct {
Foo struct {
Bar string
} `label:"allowEmpty"`
}{
Foo: struct {
Bar string
}{},
},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "false", Disabled: true, Kind: reflect.Struct},
},
},
},
},
{
desc: "level 2, struct with allowEmpty with children, value false",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Value: "false", Children: []*Node{
{Name: "Bar", Value: "hii"},
}},
},
},
structure: struct {
Foo struct {
Bar string
} `label:"allowEmpty"`
}{
Foo: struct {
Bar string
}{},
},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Foo",
FieldName: "Foo",
Value: "false",
Disabled: true,
Kind: reflect.Struct,
Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "hii", Kind: reflect.String},
},
},
},
},
},
},
{
desc: "level 2, struct pointer without children",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo"},
},
},
structure: struct {
Foo *struct {
Bar string
}
}{
Foo: &struct {
Bar string
}{},
},
expected: expected{error: true},
},
{
desc: "level 2, map without children",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo"},
},
},
structure: struct {
Foo map[string]string
}{
Foo: map[string]string{},
},
expected: expected{error: true},
},
{
desc: "level 2, pointer",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Children: []*Node{
{Name: "Bar", Value: "bir"},
}},
},
},
structure: struct {
Foo *struct {
Bar string
}
}{
Foo: &struct {
Bar string
}{},
},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Kind: reflect.Ptr, Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "bir", Kind: reflect.String},
}},
},
},
},
},
{
desc: "level 2, 2 children",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Children: []*Node{
{Name: "Bar", Value: "bir"},
{Name: "Bur", Value: "fuu"},
}},
},
},
structure: struct {
Foo struct {
Bar string
Bur string
}
}{
Foo: struct {
Bar string
Bur string
}{},
},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Foo",
FieldName: "Foo",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Bar", FieldName: "Bar", Value: "bir", Kind: reflect.String},
{Name: "Bur", FieldName: "Bur", Value: "fuu", Kind: reflect.String},
}},
},
},
},
},
{
desc: "level 3",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Children: []*Node{
{Name: "Bar", Children: []*Node{
{Name: "Bur", Value: "fuu"},
}},
}},
},
},
structure: struct {
Foo struct {
Bar struct {
Bur string
}
}
}{
Foo: struct {
Bar struct {
Bur string
}
}{},
},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Foo",
FieldName: "Foo",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Bar",
FieldName: "Bar",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Bur", FieldName: "Bur", Value: "fuu", Kind: reflect.String},
}},
}},
},
},
},
},
{
desc: "level 3, 2 children level 1, 2 children level 2, 2 children level 3",
tree: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", Children: []*Node{
{Name: "Bar", Children: []*Node{
{Name: "Fii", Value: "fii"},
{Name: "Fee", Value: "1"},
}},
{Name: "Bur", Children: []*Node{
{Name: "Faa", Value: "faa"},
}},
}},
{Name: "Fii", Children: []*Node{
{Name: "FiiBar", Value: "fiiBar"},
}},
},
},
structure: struct {
Foo struct {
Bar struct {
Fii string
Fee int
}
Bur struct {
Faa string
}
}
Fii struct {
FiiBar string
}
}{
Foo: struct {
Bar struct {
Fii string
Fee int
}
Bur struct {
Faa string
}
}{},
Fii: struct {
FiiBar string
}{},
},
expected: expected{
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Foo",
FieldName: "Foo",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Bar",
FieldName: "Bar",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Fii", FieldName: "Fii", Kind: reflect.String, Value: "fii"},
{Name: "Fee", FieldName: "Fee", Kind: reflect.Int, Value: "1"},
}},
{
Name: "Bur",
FieldName: "Bur",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Faa", FieldName: "Faa", Kind: reflect.String, Value: "faa"},
}},
}},
{
Name: "Fii",
FieldName: "Fii",
Kind: reflect.Struct,
Children: []*Node{
{Name: "FiiBar", FieldName: "FiiBar", Kind: reflect.String, Value: "fiiBar"},
}},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
err := AddMetadata(test.structure, test.tree)
if test.expected.error {
assert.Error(t, err)
} else {
require.NoError(t, err)
if !assert.Equal(t, test.expected.node, test.tree) {
bytes, errM := json.MarshalIndent(test.tree, "", " ")
require.NoError(t, errM)
fmt.Println(string(bytes))
}
}
})
}
}

View file

@ -1,12 +0,0 @@
package internal
const (
// TagLabel allow to apply a custom behavior.
// - "allowEmpty": allow to create an empty struct.
// - "-": ignore the field.
TagLabel = "label"
// TagLabelSliceAsStruct allow to use a slice of struct by creating one entry into the slice.
// The value is the substitution name use in the label to access the slice.
TagLabelSliceAsStruct = "label-slice-as-struct"
)

View file

@ -1,58 +0,0 @@
package label
import (
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/provider/label/internal"
)
// DecodeConfiguration Converts the labels to a configuration.
func DecodeConfiguration(labels map[string]string) (*config.Configuration, error) {
conf := &config.Configuration{
HTTP: &config.HTTPConfiguration{},
TCP: &config.TCPConfiguration{},
}
err := Decode(labels, conf, "traefik.http", "traefik.tcp")
if err != nil {
return nil, err
}
return conf, nil
}
// EncodeConfiguration Converts a configuration to labels.
func EncodeConfiguration(conf *config.Configuration) (map[string]string, error) {
return Encode(conf)
}
// Decode Converts the labels to an element.
// labels -> [ node -> node + metadata (type) ] -> element (node)
func Decode(labels map[string]string, element interface{}, filters ...string) error {
node, err := internal.DecodeToNode(labels, filters...)
if err != nil {
return err
}
err = internal.AddMetadata(element, node)
if err != nil {
return err
}
err = internal.Fill(element, node)
if err != nil {
return err
}
return nil
}
// Encode Converts an element to labels.
// element -> node (value) -> label (node)
func Encode(element interface{}) (map[string]string, error) {
node, err := internal.EncodeToNode(element)
if err != nil {
return nil, err
}
return internal.EncodeNode(node), nil
}

File diff suppressed because it is too large Load diff

View file

@ -10,9 +10,9 @@ import (
"strings"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/label"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/provider/label"
"github.com/gambol99/go-marathon"
)

View file

@ -31,7 +31,7 @@ func TestBuildConfiguration(t *testing.T) {
testCases := []struct {
desc string
applications *marathon.Applications
constraints types.Constraints
constraints []*types.Constraint
filterMarathonConstraints bool
defaultRule string
expected *config.Configuration
@ -1065,11 +1065,11 @@ func TestBuildConfiguration(t *testing.T) {
withTasks(localhostTask(taskPorts(80, 81))),
withLabel("traefik.tags", "foo"),
)),
constraints: types.Constraints{
&types.Constraint{
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "bar",
Value: "bar",
},
},
expected: &config.Configuration{
@ -1094,11 +1094,11 @@ func TestBuildConfiguration(t *testing.T) {
constraint("rack_id:CLUSTER:rack-1"),
)),
filterMarathonConstraints: true,
constraints: types.Constraints{
&types.Constraint{
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "rack_id:CLUSTER:rack-2",
Value: "rack_id:CLUSTER:rack-2",
},
},
expected: &config.Configuration{
@ -1123,11 +1123,11 @@ func TestBuildConfiguration(t *testing.T) {
constraint("rack_id:CLUSTER:rack-1"),
)),
filterMarathonConstraints: true,
constraints: types.Constraints{
&types.Constraint{
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "rack_id:CLUSTER:rack-1",
Value: "rack_id:CLUSTER:rack-1",
},
},
expected: &config.Configuration{
@ -1168,11 +1168,11 @@ func TestBuildConfiguration(t *testing.T) {
withLabel("traefik.tags", "bar"),
)),
constraints: types.Constraints{
&types.Constraint{
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "bar",
Value: "bar",
},
},
expected: &config.Configuration{
@ -1466,7 +1466,7 @@ func TestApplicationFilterEnabled(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
provider := &Provider{ExposedByDefault: test.exposedByDefault}
provider := &Provider{ExposedByDefault: true}
app := application(withLabel("traefik.enable", test.enabledLabel))

View file

@ -4,7 +4,7 @@ import (
"math"
"strings"
"github.com/containous/traefik/pkg/provider/label"
"github.com/containous/traefik/pkg/config/label"
"github.com/gambol99/go-marathon"
)

View file

@ -10,7 +10,6 @@ import (
"time"
"github.com/cenkalti/backoff"
"github.com/containous/flaeg/parse"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/job"
"github.com/containous/traefik/pkg/log"
@ -46,31 +45,44 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configuration of the provider.
type Provider struct {
provider.Constrainer `mapstructure:",squash" export:"true"`
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
Trace bool `description:"Display additional provider logs." export:"true"`
Watch bool `description:"Watch provider" export:"true"`
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon" export:"true"`
DefaultRule string `description:"Default rule"`
ExposedByDefault bool `description:"Expose Marathon apps by default" export:"true"`
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header" export:"true"`
FilterMarathonConstraints bool `description:"Enable use of Marathon constraints in constraint filtering" export:"true"`
TLS *types.ClientTLS `description:"Enable TLS support" export:"true"`
DialerTimeout parse.Duration `description:"Set a dialer timeout for Marathon" export:"true"`
ResponseHeaderTimeout parse.Duration `description:"Set a response header timeout for Marathon" export:"true"`
TLSHandshakeTimeout parse.Duration `description:"Set a TLS handhsake timeout for Marathon" export:"true"`
KeepAlive parse.Duration `description:"Set a TCP Keep Alive time in seconds" export:"true"`
Watch bool `description:"Watch provider." export:"true"`
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon." export:"true"`
DefaultRule string `description:"Default rule."`
ExposedByDefault bool `description:"Expose Marathon apps by default." export:"true"`
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header." export:"true"`
FilterMarathonConstraints bool `description:"Enable use of Marathon constraints in constraint filtering." export:"true"`
TLS *types.ClientTLS `description:"Enable TLS support." export:"true"`
DialerTimeout types.Duration `description:"Set a dialer timeout for Marathon." export:"true"`
ResponseHeaderTimeout types.Duration `description:"Set a response header timeout for Marathon." export:"true"`
TLSHandshakeTimeout types.Duration `description:"Set a TLS handshake timeout for Marathon." export:"true"`
KeepAlive types.Duration `description:"Set a TCP Keep Alive time." export:"true"`
ForceTaskHostname bool `description:"Force to use the task's hostname." export:"true"`
Basic *Basic `description:"Enable basic authentication" export:"true"`
RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments" export:"true"`
Basic *Basic `description:"Enable basic authentication." export:"true"`
RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments." export:"true"`
readyChecker *readinessChecker
marathonClient marathon.Marathon
defaultRuleTpl *template.Template
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Watch = true
p.Endpoint = "http://127.0.0.1:8080"
p.ExposedByDefault = true
p.DialerTimeout = types.Duration(5 * time.Second)
p.ResponseHeaderTimeout = types.Duration(60 * time.Second)
p.TLSHandshakeTimeout = types.Duration(5 * time.Second)
p.KeepAlive = types.Duration(10 * time.Second)
p.DefaultRule = DefaultTemplateRule
}
// Basic holds basic authentication specific configurations
type Basic struct {
HTTPBasicAuthUser string `description:"Basic authentication User"`
HTTPBasicPassword string `description:"Basic authentication Password"`
HTTPBasicAuthUser string `description:"Basic authentication User."`
HTTPBasicPassword string `description:"Basic authentication Password."`
}
// Init the provider

View file

@ -8,9 +8,9 @@ import (
"strings"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/label"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/provider/label"
)
func (p *Provider) buildConfiguration(ctx context.Context, services []rancherData) *config.Configuration {

View file

@ -14,7 +14,7 @@ func Test_buildConfiguration(t *testing.T) {
testCases := []struct {
desc string
containers []rancherData
constraints types.Constraints
constraints []*types.Constraint
expected *config.Configuration
}{
{
@ -330,11 +330,11 @@ func Test_buildConfiguration(t *testing.T) {
State: "",
},
},
constraints: types.Constraints{
&types.Constraint{
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "bar",
Value: "bar",
},
},
expected: &config.Configuration{
@ -363,11 +363,11 @@ func Test_buildConfiguration(t *testing.T) {
State: "",
},
},
constraints: types.Constraints{
&types.Constraint{
constraints: []*types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "foo",
Value: "foo",
},
},
expected: &config.Configuration{

View file

@ -1,7 +1,7 @@
package rancher
import (
"github.com/containous/traefik/pkg/provider/label"
"github.com/containous/traefik/pkg/config/label"
)
type configuration struct {

View file

@ -40,15 +40,26 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
provider.Constrainer `mapstructure:",squash" export:"true"`
Watch bool `description:"Watch provider" export:"true"`
DefaultRule string `description:"Default rule"`
ExposedByDefault bool `description:"Expose containers by default" export:"true"`
EnableServiceHealthFilter bool
RefreshSeconds int
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
Watch bool `description:"Watch provider." export:"true"`
DefaultRule string `description:"Default rule."`
ExposedByDefault bool `description:"Expose containers by default." export:"true"`
EnableServiceHealthFilter bool `description:"Filter services with unhealthy states and inactive states." export:"true"`
RefreshSeconds int `description:"Defines the polling interval in seconds." export:"true"`
IntervalPoll bool `description:"Poll the Rancher metadata service every 'rancher.refreshseconds' (less accurate)."`
Prefix string `description:"Prefix used for accessing the Rancher metadata service."`
defaultRuleTpl *template.Template
IntervalPoll bool `description:"Poll the Rancher metadata service every 'rancher.refreshseconds' (less accurate)"`
Prefix string `description:"Prefix used for accessing the Rancher metadata service"`
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Watch = true
p.ExposedByDefault = true
p.EnableServiceHealthFilter = true
p.RefreshSeconds = 15
p.DefaultRule = DefaultTemplateRule
p.Prefix = "latest"
}
type rancherData struct {

View file

@ -19,7 +19,13 @@ var _ provider.Provider = (*Provider)(nil)
// Provider is a provider.Provider implementation that provides a Rest API.
type Provider struct {
configurationChan chan<- config.Message
EntryPoint string `description:"EntryPoint" export:"true"`
EntryPoint string `description:"EntryPoint." export:"true"`
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.EntryPoint = "traefik"
// FIXME p.EntryPoint = static.DefaultInternalEntryPointName
}
var templatesRenderer = render.New(render.Options{Directory: "nowhere"})