From cb1d0441e925ddc10c0071df33c9c5f35bb4c58b Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 17 Jun 2020 16:48:04 +0200 Subject: [PATCH] feat: use parser to load dynamic config from file. --- docs/content/migration/v2.md | 6 +++ docs/content/providers/file.md | 2 +- integration/fixtures/acme/certificates.toml | 2 +- integration/fixtures/tcp/mixed.toml | 6 +-- integration/fixtures/timeout/keepalive.toml | 1 - .../tracing/simple-jaeger-collector.toml | 4 +- .../fixtures/tracing/simple-jaeger.toml | 4 +- .../fixtures/tracing/simple-zipkin.toml | 4 +- pkg/cli/fixtures_test.go | 2 +- pkg/config/dynamic/http_config.go | 12 ++--- pkg/config/dynamic/middlewares.go | 4 +- pkg/config/dynamic/tcp_config.go | 2 +- pkg/config/dynamic/udp_config.go | 2 +- pkg/config/file/file.go | 54 ++++++++++++++++++- pkg/config/file/file_test.go | 52 ++++++++++++++++++ pkg/config/file/fixtures_test.go | 2 +- pkg/config/parser/tags.go | 5 ++ pkg/config/static/entrypoints.go | 4 +- pkg/config/static/static_config.go | 52 +++++++++--------- pkg/provider/acme/provider.go | 6 +-- pkg/provider/file/file.go | 22 ++------ pkg/provider/file/file_test.go | 5 +- .../file/fixtures/toml/simple_file_01.toml | 2 +- .../file/fixtures/toml/simple_file_02.toml | 4 +- .../toml/template_in_directory_file02.toml | 6 +-- .../yaml/template_in_directory_file01.yml | 2 +- pkg/types/metrics.go | 8 +-- 27 files changed, 187 insertions(+), 88 deletions(-) diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index bc500eef6..5239c4a87 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -302,3 +302,9 @@ providers: --entryPoints.websecure.address=:443 --providers.kubernetesIngress=true ``` + +## v2.2 to v2.3 + +### File Provider + +The file parser has been changed, since v2.3 the unknown options/fields in a dynamic configuration file are treated as errors. diff --git a/docs/content/providers/file.md b/docs/content/providers/file.md index dc6a0b0c6..cf13f53b1 100644 --- a/docs/content/providers/file.md +++ b/docs/content/providers/file.md @@ -238,7 +238,7 @@ Thus, it's possible to define easily lot of routers, services and TLS certificat [[tls.certificates]] certFile = "/etc/traefik/cert-{{ $e }}.pem" keyFile = "/etc/traefik/cert-{{ $e }}.key" - store = ["my-store-foo-{{ $e }}", "my-store-bar-{{ $e }}"] + stores = ["my-store-foo-{{ $e }}", "my-store-bar-{{ $e }}"] {{ end }} [tls.config] diff --git a/integration/fixtures/acme/certificates.toml b/integration/fixtures/acme/certificates.toml index ba6414025..11c5a83ef 100644 --- a/integration/fixtures/acme/certificates.toml +++ b/integration/fixtures/acme/certificates.toml @@ -11,6 +11,6 @@ [http.routers.test.tls] [[tls.certificates]] - store = ["default"] + stores = ["default"] certFile = "fixtures/acme/ssl/wildcard.crt" keyFile = "fixtures/acme/ssl/wildcard.key" diff --git a/integration/fixtures/tcp/mixed.toml b/integration/fixtures/tcp/mixed.toml index ef25965ec..38d852e7e 100644 --- a/integration/fixtures/tcp/mixed.toml +++ b/integration/fixtures/tcp/mixed.toml @@ -22,7 +22,7 @@ [http.routers.my-router] rule = "Path(`/test`)" service = "whoami" - entrypoint=["tcp"] + entrypoints = ["tcp"] [http.routers.my-https-router] entryPoints=["tcp"] @@ -41,14 +41,14 @@ service = "whoami-a" entryPoints = [ "tcp" ] [tcp.routers.to-whoami-a.tls] - passthrough=true + passthrough = true [tcp.routers.to-whoami-b] rule = "HostSNI(`whoami-b.test`)" service = "whoami-b" entryPoints = [ "tcp" ] [tcp.routers.to-whoami-b.tls] - passthrough=true + passthrough = true [tcp.routers.to-whoami-no-cert] rule = "HostSNI(`whoami-c.test`)" diff --git a/integration/fixtures/timeout/keepalive.toml b/integration/fixtures/timeout/keepalive.toml index f3d7c3df7..a2b9637ef 100644 --- a/integration/fixtures/timeout/keepalive.toml +++ b/integration/fixtures/timeout/keepalive.toml @@ -31,4 +31,3 @@ passHostHeader = true [[http.services.keepalive.loadBalancer.servers]] url = "{{ .KeepAliveServer }}" - weight = 1 diff --git a/integration/fixtures/tracing/simple-jaeger-collector.toml b/integration/fixtures/tracing/simple-jaeger-collector.toml index d10d5b914..781a828e1 100644 --- a/integration/fixtures/tracing/simple-jaeger-collector.toml +++ b/integration/fixtures/tracing/simple-jaeger-collector.toml @@ -58,13 +58,13 @@ url = "http://{{.WhoAmiIP}}:{{.WhoAmiPort}}" [http.services.service2] - passHostHeader = true [http.services.service2.loadBalancer] + passHostHeader = true [[http.services.service2.loadBalancer.servers]] url = "http://{{.WhoAmiIP}}:{{.WhoAmiPort}}" [http.services.service3] - passHostHeader = true [http.services.service3.loadBalancer] + passHostHeader = true [[http.services.service3.loadBalancer.servers]] url = "http://{{.WhoAmiIP}}:{{.WhoAmiPort}}" diff --git a/integration/fixtures/tracing/simple-jaeger.toml b/integration/fixtures/tracing/simple-jaeger.toml index 056d15b4b..99269c009 100644 --- a/integration/fixtures/tracing/simple-jaeger.toml +++ b/integration/fixtures/tracing/simple-jaeger.toml @@ -57,13 +57,13 @@ url = "http://{{.WhoAmiIP}}:{{.WhoAmiPort}}" [http.services.service2] - passHostHeader = true [http.services.service2.loadBalancer] + passHostHeader = true [[http.services.service2.loadBalancer.servers]] url = "http://{{.WhoAmiIP}}:{{.WhoAmiPort}}" [http.services.service3] - passHostHeader = true [http.services.service3.loadBalancer] + passHostHeader = true [[http.services.service3.loadBalancer.servers]] url = "http://{{.WhoAmiIP}}:{{.WhoAmiPort}}" diff --git a/integration/fixtures/tracing/simple-zipkin.toml b/integration/fixtures/tracing/simple-zipkin.toml index 43842b70e..b5cfaa355 100644 --- a/integration/fixtures/tracing/simple-zipkin.toml +++ b/integration/fixtures/tracing/simple-zipkin.toml @@ -53,13 +53,13 @@ url = "http://{{.WhoAmiIP}}:{{.WhoAmiPort}}" [http.services.service2] - passHostHeader = true [http.services.service2.loadBalancer] + passHostHeader = true [[http.services.service2.loadBalancer.servers]] url = "http://{{.WhoAmiIP}}:{{.WhoAmiPort}}" [http.services.service3] - passHostHeader = true [http.services.service3.loadBalancer] + passHostHeader = true [[http.services.service3.loadBalancer.servers]] url = "http://{{.WhoAmiIP}}:{{.WhoAmiPort}}" diff --git a/pkg/cli/fixtures_test.go b/pkg/cli/fixtures_test.go index 3cdca3927..95718636f 100644 --- a/pkg/cli/fixtures_test.go +++ b/pkg/cli/fixtures_test.go @@ -4,7 +4,7 @@ type Yo struct { Foo string `description:"Foo description"` Fii string `description:"Fii description"` Fuu string `description:"Fuu description"` - Yi *Yi `label:"allowEmpty"` + Yi *Yi `label:"allowEmpty" file:"allowEmpty"` Yu *Yi } diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 27c5e44f4..ad99ba8e8 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -21,7 +21,7 @@ type HTTPConfiguration struct { // Model is a set of default router's values. type Model struct { Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty"` - TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty"` + TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` } // +k8s:deepcopy-gen=true @@ -42,7 +42,7 @@ type Router struct { Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty"` Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"` Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty"` - TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty"` + TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` } // +k8s:deepcopy-gen=true @@ -103,7 +103,7 @@ func (w *WRRService) SetDefaults() { // Sticky holds the sticky configuration. type Sticky struct { - Cookie *Cookie `json:"cookie,omitempty" toml:"cookie,omitempty" yaml:"cookie,omitempty" label:"allowEmpty"` + Cookie *Cookie `json:"cookie,omitempty" toml:"cookie,omitempty" yaml:"cookie,omitempty" label:"allowEmpty" file:"allowEmpty"` } // +k8s:deepcopy-gen=true @@ -120,7 +120,7 @@ type Cookie struct { // ServersLoadBalancer holds the ServersLoadBalancer configuration. type ServersLoadBalancer struct { - Sticky *Sticky `json:"sticky,omitempty" toml:"sticky,omitempty" yaml:"sticky,omitempty" label:"allowEmpty"` + Sticky *Sticky `json:"sticky,omitempty" toml:"sticky,omitempty" yaml:"sticky,omitempty" label:"allowEmpty" file:"allowEmpty"` Servers []Server `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server"` HealthCheck *HealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty"` PassHostHeader *bool `json:"passHostHeader" toml:"passHostHeader" yaml:"passHostHeader"` @@ -162,8 +162,8 @@ type ResponseForwarding struct { // Server holds the server configuration. type Server struct { URL string `json:"url,omitempty" toml:"url,omitempty" yaml:"url,omitempty" label:"-"` - Scheme string `toml:"-" json:"-" yaml:"-"` - Port string `toml:"-" json:"-" yaml:"-"` + Scheme string `toml:"-" json:"-" yaml:"-" file:"-"` + Port string `toml:"-" json:"-" yaml:"-" file:"-"` } // SetDefaults Default values for a Server. diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 8727b818b..acdb8f3a9 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -34,7 +34,7 @@ type Middleware struct { InFlightReq *InFlightReq `json:"inFlightReq,omitempty" toml:"inFlightReq,omitempty" yaml:"inFlightReq,omitempty"` Buffering *Buffering `json:"buffering,omitempty" toml:"buffering,omitempty" yaml:"buffering,omitempty"` CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty" toml:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"` - Compress *Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty"` + Compress *Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty" file:"allowEmpty"` PassTLSClientCert *PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty"` Retry *Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty"` ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty"` @@ -275,7 +275,7 @@ func (s *IPStrategy) Get() (ip.Strategy, error) { // IPWhiteList holds the ip white list configuration. type IPWhiteList struct { SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` - IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty" label:"allowEmpty"` + IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty" label:"allowEmpty" file:"allowEmpty"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/dynamic/tcp_config.go b/pkg/config/dynamic/tcp_config.go index 158f3e113..88592f5fb 100644 --- a/pkg/config/dynamic/tcp_config.go +++ b/pkg/config/dynamic/tcp_config.go @@ -50,7 +50,7 @@ type TCPRouter struct { EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty"` Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty"` Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"` - TLS *RouterTCPTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty"` + TLS *RouterTCPTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/dynamic/udp_config.go b/pkg/config/dynamic/udp_config.go index c890445a5..f94aa1095 100644 --- a/pkg/config/dynamic/udp_config.go +++ b/pkg/config/dynamic/udp_config.go @@ -78,5 +78,5 @@ func (l *UDPServersLoadBalancer) Mergeable(loadBalancer *UDPServersLoadBalancer) // UDPServer defines a UDP server configuration. type UDPServer struct { Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty" label:"-"` - Port string `toml:"-" json:"-" yaml:"-"` + Port string `toml:"-" json:"-" yaml:"-" file:"-"` } diff --git a/pkg/config/file/file.go b/pkg/config/file/file.go index 1bd48da8f..f55ec2fe3 100644 --- a/pkg/config/file/file.go +++ b/pkg/config/file/file.go @@ -2,7 +2,11 @@ package file import ( + "fmt" + + "github.com/BurntSushi/toml" "github.com/containous/traefik/v2/pkg/config/parser" + "gopkg.in/yaml.v2" ) // Decode decodes the given configuration file into the given element. @@ -22,11 +26,57 @@ func Decode(filePath string, element interface{}) error { return err } - metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: true} + metaOpts := parser.MetadataOpts{TagName: parser.TagFile, AllowSliceAsStruct: false} err = parser.AddMetadata(element, root, metaOpts) if err != nil { return err } - return parser.Fill(element, root, parser.FillerOpts{AllowSliceAsStruct: true}) + return parser.Fill(element, root, parser.FillerOpts{AllowSliceAsStruct: false}) +} + +// DecodeContent decodes the given configuration file content into the given element. +// The operation goes through three stages roughly summarized as: +// file contents -> tree of untyped nodes +// untyped nodes -> nodes augmented with metadata such as kind (inferred from element) +// "typed" nodes -> typed element. +func DecodeContent(content string, extension string, element interface{}) error { + data := make(map[string]interface{}) + + switch extension { + case ".toml": + _, err := toml.Decode(content, &data) + if err != nil { + return err + } + + case ".yml", ".yaml": + var err error + err = yaml.Unmarshal([]byte(content), &data) + if err != nil { + return err + } + + default: + return fmt.Errorf("unsupported file extension: %s", extension) + } + + filters := getRootFieldNames(element) + + node, err := decodeRawToNode(data, parser.DefaultRootName, filters...) + if err != nil { + return err + } + + if len(node.Children) == 0 { + return nil + } + + metaOpts := parser.MetadataOpts{TagName: parser.TagFile, AllowSliceAsStruct: false} + err = parser.AddMetadata(element, node, metaOpts) + if err != nil { + return err + } + + return parser.Fill(element, node, parser.FillerOpts{AllowSliceAsStruct: false}) } diff --git a/pkg/config/file/file_test.go b/pkg/config/file/file_test.go index c769514e3..138ecebce 100644 --- a/pkg/config/file/file_test.go +++ b/pkg/config/file/file_test.go @@ -42,6 +42,32 @@ fii = "bir" assert.Equal(t, expected, element) } +func TestDecodeContent_TOML(t *testing.T) { + content := ` +foo = "bar" +fii = "bir" +[yi] +` + + element := &Yo{ + Fuu: "test", + } + + err := DecodeContent(content, ".toml", element) + require.NoError(t, err) + + expected := &Yo{ + Foo: "bar", + Fii: "bir", + Fuu: "test", + Yi: &Yi{ + Foo: "foo", + Fii: "fii", + }, + } + assert.Equal(t, expected, element) +} + func TestDecode_YAML(t *testing.T) { f, err := ioutil.TempFile("", "traefik-config-*.yaml") require.NoError(t, err) @@ -74,3 +100,29 @@ yi: {} } assert.Equal(t, expected, element) } + +func TestDecodeContent_YAML(t *testing.T) { + content := ` +foo: bar +fii: bir +yi: {} +` + + element := &Yo{ + Fuu: "test", + } + + err := DecodeContent(content, ".yaml", element) + require.NoError(t, err) + + expected := &Yo{ + Foo: "bar", + Fii: "bir", + Fuu: "test", + Yi: &Yi{ + Foo: "foo", + Fii: "fii", + }, + } + assert.Equal(t, expected, element) +} diff --git a/pkg/config/file/fixtures_test.go b/pkg/config/file/fixtures_test.go index 25521e8a3..9904f82d1 100644 --- a/pkg/config/file/fixtures_test.go +++ b/pkg/config/file/fixtures_test.go @@ -6,7 +6,7 @@ type Yo struct { Foo string Fii string Fuu string - Yi *Yi `label:"allowEmpty"` + Yi *Yi `file:"allowEmpty"` } func (y *Yo) SetDefaults() { diff --git a/pkg/config/parser/tags.go b/pkg/config/parser/tags.go index a4c479aea..3304c45c4 100644 --- a/pkg/config/parser/tags.go +++ b/pkg/config/parser/tags.go @@ -6,6 +6,11 @@ const ( // - "-": ignore the field. TagLabel = "label" + // TagFile allows to apply a custom behavior. + // - "allowEmpty": allows to create an empty struct. + // - "-": ignore the field. + TagFile = "file" + // TagLabelSliceAsStruct allows to use a slice of struct by creating one entry into the slice. // The value is the substitution name used in the label to access the slice. TagLabelSliceAsStruct = "label-slice-as-struct" diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 1c0e030ca..88c4ba8df 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -12,7 +12,7 @@ import ( type EntryPoint struct { Address string `description:"Entry point address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` Transport *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty"` - ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty"` + ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty"` ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty"` HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty"` } @@ -51,7 +51,7 @@ func (ep *EntryPoint) SetDefaults() { type HTTPConfig struct { Redirections *Redirections `description:"Set of redirection" json:"redirections,omitempty" toml:"redirections,omitempty" yaml:"redirections,omitempty"` Middlewares []string `description:"Default middlewares for the routers linked to the entry point." json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty"` - TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty"` + TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"` } // Redirections is a set of redirection for an entry point. diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 1fac9e019..91f5e68c6 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -57,15 +57,15 @@ type Configuration struct { EntryPoints EntryPoints `description:"Entry points definition." json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"` Providers *Providers `description:"Providers configuration." json:"providers,omitempty" toml:"providers,omitempty" yaml:"providers,omitempty" export:"true"` - API *API `description:"Enable api/dashboard." json:"api,omitempty" toml:"api,omitempty" yaml:"api,omitempty" label:"allowEmpty" export:"true"` + API *API `description:"Enable api/dashboard." json:"api,omitempty" toml:"api,omitempty" yaml:"api,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Metrics *types.Metrics `description:"Enable a metrics exporter." json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` - Ping *ping.Handler `description:"Enable ping." json:"ping,omitempty" toml:"ping,omitempty" yaml:"ping,omitempty" label:"allowEmpty" export:"true"` + Ping *ping.Handler `description:"Enable ping." json:"ping,omitempty" toml:"ping,omitempty" yaml:"ping,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Log *types.TraefikLog `description:"Traefik log settings." json:"log,omitempty" toml:"log,omitempty" yaml:"log,omitempty" label:"allowEmpty" export:"true"` - AccessLog *types.AccessLog `description:"Access log settings." json:"accessLog,omitempty" toml:"accessLog,omitempty" yaml:"accessLog,omitempty" label:"allowEmpty" export:"true"` - Tracing *Tracing `description:"OpenTracing configuration." json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" label:"allowEmpty" export:"true"` + Log *types.TraefikLog `description:"Traefik log settings." json:"log,omitempty" toml:"log,omitempty" yaml:"log,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + AccessLog *types.AccessLog `description:"Access log settings." json:"accessLog,omitempty" toml:"accessLog,omitempty" yaml:"accessLog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Tracing *Tracing `description:"OpenTracing configuration." json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - HostResolver *types.HostResolverConfig `description:"Enable CNAME Flattening." json:"hostResolver,omitempty" toml:"hostResolver,omitempty" yaml:"hostResolver,omitempty" label:"allowEmpty" export:"true"` + HostResolver *types.HostResolverConfig `description:"Enable CNAME Flattening." json:"hostResolver,omitempty" toml:"hostResolver,omitempty" yaml:"hostResolver,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` CertificatesResolvers map[string]CertificateResolver `description:"Certificates resolvers configuration." json:"certificatesResolvers,omitempty" toml:"certificatesResolvers,omitempty" yaml:"certificatesResolvers,omitempty" export:"true"` } @@ -77,8 +77,8 @@ type CertificateResolver struct { // Global holds the global configuration. type Global struct { - CheckNewVersion bool `description:"Periodically check if a new version has been released." json:"checkNewVersion,omitempty" toml:"checkNewVersion,omitempty" yaml:"checkNewVersion,omitempty" label:"allowEmpty" export:"true"` - SendAnonymousUsage bool `description:"Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default." json:"sendAnonymousUsage,omitempty" toml:"sendAnonymousUsage,omitempty" yaml:"sendAnonymousUsage,omitempty" label:"allowEmpty" export:"true"` + CheckNewVersion bool `description:"Periodically check if a new version has been released." json:"checkNewVersion,omitempty" toml:"checkNewVersion,omitempty" yaml:"checkNewVersion,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + SendAnonymousUsage bool `description:"Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default." json:"sendAnonymousUsage,omitempty" toml:"sendAnonymousUsage,omitempty" yaml:"sendAnonymousUsage,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // ServersTransport options to configure communication between Traefik and the servers. @@ -95,8 +95,8 @@ type API struct { Dashboard bool `description:"Activate dashboard." json:"dashboard,omitempty" toml:"dashboard,omitempty" yaml:"dashboard,omitempty" export:"true"` Debug bool `description:"Enable additional endpoints for debugging and profiling." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"` // TODO: Re-enable statistics - // Statistics *types.Statistics `description:"Enable more detailed statistics." json:"statistics,omitempty" toml:"statistics,omitempty" yaml:"statistics,omitempty" export:"true" label:"allowEmpty"` - DashboardAssets *assetfs.AssetFS `json:"-" toml:"-" yaml:"-" label:"-"` + // Statistics *types.Statistics `description:"Enable more detailed statistics." json:"statistics,omitempty" toml:"statistics,omitempty" yaml:"statistics,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + DashboardAssets *assetfs.AssetFS `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` } // SetDefaults sets the default values. @@ -144,12 +144,12 @@ func (a *LifeCycle) SetDefaults() { type Tracing struct { ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)." json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty" export:"true"` - Jaeger *jaeger.Config `description:"Settings for Jaeger." json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" export:"true" label:"allowEmpty"` - Zipkin *zipkin.Config `description:"Settings for Zipkin." json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" export:"true" label:"allowEmpty"` - Datadog *datadog.Config `description:"Settings for Datadog." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" export:"true" label:"allowEmpty"` - Instana *instana.Config `description:"Settings for Instana." json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" export:"true" label:"allowEmpty"` - Haystack *haystack.Config `description:"Settings for Haystack." json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" export:"true" label:"allowEmpty"` - Elastic *elastic.Config `description:"Settings for Elastic." json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" export:"true" label:"allowEmpty"` + Jaeger *jaeger.Config `description:"Settings for Jaeger." json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Zipkin *zipkin.Config `description:"Settings for Zipkin." json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Datadog *datadog.Config `description:"Settings for Datadog." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Instana *instana.Config `description:"Settings for Instana." json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Haystack *haystack.Config `description:"Settings for Haystack." json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Elastic *elastic.Config `description:"Settings for Elastic." json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` } // SetDefaults sets the default values. @@ -162,19 +162,19 @@ func (t *Tracing) SetDefaults() { type Providers struct { ProvidersThrottleDuration types.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"` - Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" export:"true" label:"allowEmpty"` + Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"` - Marathon *marathon.Provider `description:"Enable Marathon backend with default settings." json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" export:"true" label:"allowEmpty"` - KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" export:"true" label:"allowEmpty"` - KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" export:"true" label:"allowEmpty"` - Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty"` - Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty"` + Marathon *marathon.Provider `description:"Enable Marathon backend with default settings." json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` ConsulCatalog *consulcatalog.Provider `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty"` - Consul *consul.Provider `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" export:"true" label:"allowEmpty"` - Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" export:"true" label:"allowEmpty"` - ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" export:"true" label:"allowEmpty"` - Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" export:"true" label:"allowEmpty"` + Consul *consul.Provider `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` } // SetEffectiveConfiguration adds missing configuration parameters derived from existing ones. diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 0f9ef03de..14309375f 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -38,9 +38,9 @@ type Configuration struct { CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"` Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty"` KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty"` - DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty"` - HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty"` - TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty"` + DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty"` + HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty" file:"allowEmpty"` + TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty"` } // SetDefaults sets the default values. diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index 879023a00..e7e33bd75 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -11,15 +11,14 @@ import ( "strings" "text/template" - "github.com/BurntSushi/toml" "github.com/Masterminds/sprig" "github.com/containous/traefik/v2/pkg/config/dynamic" + "github.com/containous/traefik/v2/pkg/config/file" "github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/provider" "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/tls" "gopkg.in/fsnotify.v1" - "gopkg.in/yaml.v2" ) const providerName = "file" @@ -418,22 +417,9 @@ func (p *Provider) decodeConfiguration(filePath string, content string) (*dynami }, } - switch strings.ToLower(filepath.Ext(filePath)) { - case ".toml": - _, err := toml.Decode(content, configuration) - if err != nil { - return nil, err - } - - case ".yml", ".yaml": - var err error - err = yaml.Unmarshal([]byte(content), configuration) - if err != nil { - return nil, err - } - - default: - return nil, fmt.Errorf("unsupported file extension: %s", filePath) + err := file.DecodeContent(content, strings.ToLower(filepath.Ext(filePath)), configuration) + if err != nil { + return nil, err } return configuration, nil diff --git a/pkg/provider/file/file_test.go b/pkg/provider/file/file_test.go index fa7fd39ed..7581564f4 100644 --- a/pkg/provider/file/file_test.go +++ b/pkg/provider/file/file_test.go @@ -90,11 +90,12 @@ func TestProvideWithoutWatch(t *testing.T) { timeout := time.After(time.Second) select { case conf := <-configChan: + require.NotNil(t, conf.Configuration.HTTP) numServices := len(conf.Configuration.HTTP.Services) + len(conf.Configuration.TCP.Services) + len(conf.Configuration.UDP.Services) numRouters := len(conf.Configuration.HTTP.Routers) + len(conf.Configuration.TCP.Routers) + len(conf.Configuration.UDP.Routers) - assert.Equal(t, numServices, test.expectedNumService) - assert.Equal(t, numRouters, test.expectedNumRouter) + assert.Equal(t, test.expectedNumService, numServices) + assert.Equal(t, test.expectedNumRouter, numRouters) require.NotNil(t, conf.Configuration.TLS) assert.Len(t, conf.Configuration.TLS.Certificates, test.expectedNumTLSConf) assert.Len(t, conf.Configuration.TLS.Options, test.expectedNumTLSOptions) diff --git a/pkg/provider/file/fixtures/toml/simple_file_01.toml b/pkg/provider/file/fixtures/toml/simple_file_01.toml index bb3df5d87..8fe23ab40 100644 --- a/pkg/provider/file/fixtures/toml/simple_file_01.toml +++ b/pkg/provider/file/fixtures/toml/simple_file_01.toml @@ -1,5 +1,5 @@ [http.routers] - + [http.routers."router1"] service = "application-1" diff --git a/pkg/provider/file/fixtures/toml/simple_file_02.toml b/pkg/provider/file/fixtures/toml/simple_file_02.toml index b7ef74001..c8da9db80 100644 --- a/pkg/provider/file/fixtures/toml/simple_file_02.toml +++ b/pkg/provider/file/fixtures/toml/simple_file_02.toml @@ -66,7 +66,7 @@ [tcp.services.applicationtcp-1.loadBalancer] [[tcp.services.applicationtcp-1.loadBalancer.servers]] - url = "http://172.17.0.9:80" + address = "http://172.17.0.9:80" [udp.routers] @@ -77,4 +77,4 @@ [udp.services.applicationudp-1.loadBalancer] [[udp.services.applicationudp-1.loadBalancer.servers]] - url = "http://172.17.0.10:80" + address = "http://172.17.0.10:80" diff --git a/pkg/provider/file/fixtures/toml/template_in_directory_file02.toml b/pkg/provider/file/fixtures/toml/template_in_directory_file02.toml index 35c2d25f3..07dadaf9c 100644 --- a/pkg/provider/file/fixtures/toml/template_in_directory_file02.toml +++ b/pkg/provider/file/fixtures/toml/template_in_directory_file02.toml @@ -1,6 +1,6 @@ [http.services] {{ range $i, $e := until 20 }} - [http.services.application-{{ $e }}] - [[http.services.application-{{ $e }}.servers]] - url="http://127.0.0.1" + [http.services.application-{{ $e }}.loadBalancer] + [[http.services.application-{{ $e }}.loadBalancer.servers]] + url = "http://127.0.0.1" {{ end }} \ No newline at end of file diff --git a/pkg/provider/file/fixtures/yaml/template_in_directory_file01.yml b/pkg/provider/file/fixtures/yaml/template_in_directory_file01.yml index 908eed00c..185740d81 100644 --- a/pkg/provider/file/fixtures/yaml/template_in_directory_file01.yml +++ b/pkg/provider/file/fixtures/yaml/template_in_directory_file01.yml @@ -1,6 +1,6 @@ http: -{{ range $i, $e := until 20 }} routers: +{{ range $i, $e := until 20 }} router{{ $e }}: service: application-1 {{ end }} diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go index de0c08283..41d835249 100644 --- a/pkg/types/metrics.go +++ b/pkg/types/metrics.go @@ -6,10 +6,10 @@ import ( // Metrics provides options to expose and send Traefik metrics to different third party monitoring systems. type Metrics struct { - Prometheus *Prometheus `description:"Prometheus metrics exporter type." json:"prometheus,omitempty" toml:"prometheus,omitempty" yaml:"prometheus,omitempty" export:"true" label:"allowEmpty"` - Datadog *Datadog `description:"Datadog metrics exporter type." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" export:"true" label:"allowEmpty"` - StatsD *Statsd `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" export:"true" label:"allowEmpty"` - InfluxDB *InfluxDB `description:"InfluxDB metrics exporter type." json:"influxDB,omitempty" toml:"influxDB,omitempty" yaml:"influxDB,omitempty" label:"allowEmpty"` + Prometheus *Prometheus `description:"Prometheus metrics exporter type." json:"prometheus,omitempty" toml:"prometheus,omitempty" yaml:"prometheus,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Datadog *Datadog `description:"Datadog metrics exporter type." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + StatsD *Statsd `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + InfluxDB *InfluxDB `description:"InfluxDB metrics exporter type." json:"influxDB,omitempty" toml:"influxDB,omitempty" yaml:"influxDB,omitempty" label:"allowEmpty" file:"allowEmpty"` } // Prometheus can contain specific configuration used by the Prometheus Metrics exporter.