2017-04-17 10:50:02 +00:00
package file
2015-09-07 13:25:13 +00:00
import (
2019-03-27 14:02:06 +00:00
"bytes"
2018-11-14 09:18:03 +00:00
"context"
2019-04-01 13:30:07 +00:00
"errors"
2017-05-26 13:32:03 +00:00
"fmt"
"io/ioutil"
2017-11-09 11:16:03 +00:00
"os"
"path/filepath"
2015-09-07 15:39:22 +00:00
"strings"
2018-03-22 15:14:04 +00:00
"text/template"
2015-09-24 15:16:13 +00:00
2019-03-27 14:02:06 +00:00
"github.com/BurntSushi/toml"
"github.com/Masterminds/sprig"
2019-08-03 01:58:23 +00:00
"github.com/containous/traefik/v2/pkg/config/dynamic"
"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"
2015-09-24 15:16:13 +00:00
"gopkg.in/fsnotify.v1"
2019-06-26 16:18:04 +00:00
"gopkg.in/yaml.v2"
2015-09-07 13:25:13 +00:00
)
2018-11-14 09:18:03 +00:00
const providerName = "file"
2017-04-17 10:50:02 +00:00
var _ provider . Provider = ( * Provider ) ( nil )
2016-08-16 17:13:18 +00:00
2017-04-17 10:50:02 +00:00
// Provider holds configurations of the provider.
type Provider struct {
2019-09-26 07:24:04 +00:00
Directory string ` description:"Load dynamic configuration from one or more .toml or .yml files in a directory." json:"directory,omitempty" toml:"directory,omitempty" yaml:"directory,omitempty" export:"true" `
2019-07-01 09:30:05 +00:00
Watch bool ` description:"Watch provider." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true" `
2019-09-26 07:24:04 +00:00
Filename string ` description:"Load dynamic configuration from a file." json:"filename,omitempty" toml:"filename,omitempty" yaml:"filename,omitempty" export:"true" `
2019-07-01 09:30:05 +00:00
DebugLogGeneratedTemplate bool ` description:"Enable debug logging of generated configuration template." json:"debugLogGeneratedTemplate,omitempty" toml:"debugLogGeneratedTemplate,omitempty" yaml:"debugLogGeneratedTemplate,omitempty" export:"true" `
2019-06-17 09:48:05 +00:00
}
// SetDefaults sets the default values.
func ( p * Provider ) SetDefaults ( ) {
p . Watch = true
p . Filename = ""
2015-09-07 13:25:13 +00:00
}
2020-05-11 10:06:07 +00:00
// Init the provider.
2018-11-27 16:42:04 +00:00
func ( p * Provider ) Init ( ) error {
2019-03-27 14:02:06 +00:00
return nil
2018-07-11 07:08:03 +00:00
}
2017-04-17 10:50:02 +00:00
// Provide allows the file provider to provide configurations to traefik
2015-11-01 18:29:47 +00:00
// using the given configuration channel.
2019-07-10 07:26:04 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- dynamic . Message , pool * safe . Pool ) error {
2017-12-02 18:25:29 +00:00
configuration , err := p . BuildConfiguration ( )
2015-09-07 13:25:13 +00:00
if err != nil {
2015-10-01 10:04:25 +00:00
return err
2015-09-07 13:25:13 +00:00
}
2017-05-26 13:32:03 +00:00
if p . Watch {
var watchItem string
2019-02-05 16:10:03 +00:00
switch {
case len ( p . Directory ) > 0 :
2017-05-26 13:32:03 +00:00
watchItem = p . Directory
2019-02-05 16:10:03 +00:00
case len ( p . Filename ) > 0 :
2017-11-09 11:16:03 +00:00
watchItem = filepath . Dir ( p . Filename )
2019-02-05 16:10:03 +00:00
default :
2019-07-15 08:22:03 +00:00
return errors . New ( "error using file configuration provider, neither filename or directory defined" )
2017-05-26 13:32:03 +00:00
}
if err := p . addWatcher ( pool , watchItem , configurationChan , p . watcherCallback ) ; err != nil {
return err
}
}
sendConfigToChannel ( configurationChan , configuration )
return nil
}
2020-05-11 10:06:07 +00:00
// BuildConfiguration loads configuration either from file or a directory
// specified by 'Filename'/'Directory' and returns a 'Configuration' object.
2019-07-10 07:26:04 +00:00
func ( p * Provider ) BuildConfiguration ( ) ( * dynamic . Configuration , error ) {
2018-11-14 09:18:03 +00:00
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , providerName ) )
2018-05-22 10:02:03 +00:00
if len ( p . Directory ) > 0 {
2018-11-14 09:18:03 +00:00
return p . loadFileConfigFromDirectory ( ctx , p . Directory , nil )
2017-11-21 09:24:03 +00:00
}
2018-05-22 10:02:03 +00:00
if len ( p . Filename ) > 0 {
2019-09-13 17:28:04 +00:00
return p . loadFileConfig ( ctx , p . Filename , true )
2018-05-22 10:02:03 +00:00
}
2019-07-15 08:22:03 +00:00
return nil , errors . New ( "error using file configuration provider, neither filename or directory defined" )
2017-11-21 09:24:03 +00:00
}
2019-07-10 07:26:04 +00:00
func ( p * Provider ) addWatcher ( pool * safe . Pool , directory string , configurationChan chan <- dynamic . Message , callback func ( chan <- dynamic . Message , fsnotify . Event ) ) error {
2017-05-26 13:32:03 +00:00
watcher , err := fsnotify . NewWatcher ( )
2015-09-07 13:25:13 +00:00
if err != nil {
2020-05-11 10:06:07 +00:00
return fmt . Errorf ( "error creating file watcher: %w" , err )
2015-09-07 13:25:13 +00:00
}
2018-05-22 10:02:03 +00:00
err = watcher . Add ( directory )
if err != nil {
2020-05-11 10:06:07 +00:00
return fmt . Errorf ( "error adding file watcher: %w" , err )
2018-05-22 10:02:03 +00:00
}
2017-05-26 13:32:03 +00:00
// Process events
2020-02-03 16:56:04 +00:00
pool . GoCtx ( func ( ctx context . Context ) {
2017-05-26 13:32:03 +00:00
defer watcher . Close ( )
for {
select {
2020-02-03 16:56:04 +00:00
case <- ctx . Done ( ) :
2017-05-26 13:32:03 +00:00
return
case evt := <- watcher . Events :
2017-11-09 11:16:03 +00:00
if p . Directory == "" {
_ , evtFileName := filepath . Split ( evt . Name )
2019-07-15 08:22:03 +00:00
_ , confFileName := filepath . Split ( p . Filename )
2017-11-09 11:16:03 +00:00
if evtFileName == confFileName {
callback ( configurationChan , evt )
}
} else {
callback ( configurationChan , evt )
}
2017-05-26 13:32:03 +00:00
case err := <- watcher . Errors :
2018-11-14 09:18:03 +00:00
log . WithoutContext ( ) . WithField ( log . ProviderName , providerName ) . Errorf ( "Watcher event error: %s" , err )
2015-09-07 13:25:13 +00:00
}
2015-10-03 14:50:53 +00:00
}
2017-05-26 13:32:03 +00:00
} )
return nil
}
2019-07-10 07:26:04 +00:00
func ( p * Provider ) watcherCallback ( configurationChan chan <- dynamic . Message , event fsnotify . Event ) {
2019-07-15 08:22:03 +00:00
watchItem := p . Filename
2018-05-22 10:02:03 +00:00
if len ( p . Directory ) > 0 {
2017-11-21 09:24:03 +00:00
watchItem = p . Directory
}
2018-11-14 09:18:03 +00:00
logger := log . WithoutContext ( ) . WithField ( log . ProviderName , providerName )
2017-11-21 09:24:03 +00:00
if _ , err := os . Stat ( watchItem ) ; err != nil {
2018-11-14 09:18:03 +00:00
logger . Errorf ( "Unable to watch %s : %v" , watchItem , err )
2017-11-21 09:24:03 +00:00
return
}
2017-12-02 18:25:29 +00:00
configuration , err := p . BuildConfiguration ( )
2017-11-21 09:24:03 +00:00
if err != nil {
2018-11-14 09:18:03 +00:00
logger . Errorf ( "Error occurred during watcher callback: %s" , err )
2017-11-21 09:24:03 +00:00
return
}
sendConfigToChannel ( configurationChan , configuration )
}
2019-07-10 07:26:04 +00:00
func sendConfigToChannel ( configurationChan chan <- dynamic . Message , configuration * dynamic . Configuration ) {
configurationChan <- dynamic . Message {
2015-11-13 10:50:32 +00:00
ProviderName : "file" ,
Configuration : configuration ,
}
2015-09-07 13:25:13 +00:00
}
2019-09-13 17:28:04 +00:00
func ( p * Provider ) loadFileConfig ( ctx context . Context , filename string , parseTemplate bool ) ( * dynamic . Configuration , error ) {
2019-06-26 16:18:04 +00:00
var err error
2019-07-10 07:26:04 +00:00
var configuration * dynamic . Configuration
2018-05-22 10:02:03 +00:00
if parseTemplate {
2019-09-13 17:28:04 +00:00
configuration , err = p . CreateConfiguration ( ctx , filename , template . FuncMap { } , false )
2018-05-22 10:02:03 +00:00
} else {
2019-06-26 16:18:04 +00:00
configuration , err = p . DecodeConfiguration ( filename )
2018-05-22 10:02:03 +00:00
}
2018-03-22 15:14:04 +00:00
if err != nil {
return nil , err
}
2018-11-14 09:18:03 +00:00
2019-06-27 21:58:03 +00:00
if configuration . TLS != nil {
2019-09-13 17:28:04 +00:00
configuration . TLS . Certificates = flattenCertificates ( ctx , configuration . TLS )
2019-06-27 21:58:03 +00:00
}
return configuration , nil
}
2019-09-13 17:28:04 +00:00
func flattenCertificates ( ctx context . Context , tlsConfig * dynamic . TLSConfiguration ) [ ] * tls . CertAndStores {
2019-06-27 21:58:03 +00:00
var certs [ ] * tls . CertAndStores
for _ , cert := range tlsConfig . Certificates {
content , err := cert . Certificate . CertFile . Read ( )
2019-01-29 15:46:09 +00:00
if err != nil {
2019-09-13 17:28:04 +00:00
log . FromContext ( ctx ) . Error ( err )
2019-01-29 15:46:09 +00:00
continue
}
2019-06-27 21:58:03 +00:00
cert . Certificate . CertFile = tls . FileOrContent ( string ( content ) )
2019-01-29 15:46:09 +00:00
2019-06-27 21:58:03 +00:00
content , err = cert . Certificate . KeyFile . Read ( )
2019-01-29 15:46:09 +00:00
if err != nil {
2019-09-13 17:28:04 +00:00
log . FromContext ( ctx ) . Error ( err )
2019-01-29 15:46:09 +00:00
continue
}
2019-06-27 21:58:03 +00:00
cert . Certificate . KeyFile = tls . FileOrContent ( string ( content ) )
certs = append ( certs , cert )
2019-01-29 15:46:09 +00:00
}
2019-06-27 21:58:03 +00:00
return certs
2017-05-26 13:32:03 +00:00
}
2019-07-10 07:26:04 +00:00
func ( p * Provider ) loadFileConfigFromDirectory ( ctx context . Context , directory string , configuration * dynamic . Configuration ) ( * dynamic . Configuration , error ) {
2017-05-26 13:32:03 +00:00
fileList , err := ioutil . ReadDir ( directory )
if err != nil {
2020-05-11 10:06:07 +00:00
return configuration , fmt . Errorf ( "unable to read directory %s: %w" , directory , err )
2017-05-26 13:32:03 +00:00
}
2017-11-09 11:16:03 +00:00
if configuration == nil {
2019-07-10 07:26:04 +00:00
configuration = & dynamic . Configuration {
HTTP : & dynamic . HTTPConfiguration {
Routers : make ( map [ string ] * dynamic . Router ) ,
Middlewares : make ( map [ string ] * dynamic . Middleware ) ,
Services : make ( map [ string ] * dynamic . Service ) ,
2019-03-14 08:30:04 +00:00
} ,
2019-07-10 07:26:04 +00:00
TCP : & dynamic . TCPConfiguration {
Routers : make ( map [ string ] * dynamic . TCPRouter ) ,
Services : make ( map [ string ] * dynamic . TCPService ) ,
2019-03-14 08:30:04 +00:00
} ,
2019-07-10 07:26:04 +00:00
TLS : & dynamic . TLSConfiguration {
2019-06-27 21:58:03 +00:00
Stores : make ( map [ string ] tls . Store ) ,
Options : make ( map [ string ] tls . Options ) ,
} ,
2020-02-11 00:26:04 +00:00
UDP : & dynamic . UDPConfiguration {
Routers : make ( map [ string ] * dynamic . UDPRouter ) ,
Services : make ( map [ string ] * dynamic . UDPService ) ,
} ,
2017-11-09 11:16:03 +00:00
}
2015-09-07 13:25:13 +00:00
}
2017-05-26 13:32:03 +00:00
2019-06-27 21:58:03 +00:00
configTLSMaps := make ( map [ * tls . CertAndStores ] struct { } )
2019-06-26 16:18:04 +00:00
2017-11-09 11:16:03 +00:00
for _ , item := range fileList {
2019-10-11 15:20:05 +00:00
logger := log . FromContext ( log . With ( ctx , log . Str ( "filename" , item . Name ( ) ) ) )
2017-11-09 11:16:03 +00:00
if item . IsDir ( ) {
2018-11-14 09:18:03 +00:00
configuration , err = p . loadFileConfigFromDirectory ( ctx , filepath . Join ( directory , item . Name ( ) ) , configuration )
2017-11-09 11:16:03 +00:00
if err != nil {
2020-05-11 10:06:07 +00:00
return configuration , fmt . Errorf ( "unable to load content configuration from subdirectory %s: %w" , item , err )
2017-11-09 11:16:03 +00:00
}
continue
2019-06-26 16:18:04 +00:00
}
switch strings . ToLower ( filepath . Ext ( item . Name ( ) ) ) {
case ".toml" , ".yaml" , ".yml" :
// noop
default :
2017-05-26 13:32:03 +00:00
continue
}
2019-07-10 07:26:04 +00:00
var c * dynamic . Configuration
2019-09-13 17:28:04 +00:00
c , err = p . loadFileConfig ( ctx , filepath . Join ( directory , item . Name ( ) ) , true )
2017-05-26 13:32:03 +00:00
if err != nil {
2020-05-11 10:06:07 +00:00
return configuration , fmt . Errorf ( "%s: %w" , filepath . Join ( directory , item . Name ( ) ) , err )
2017-05-26 13:32:03 +00:00
}
2019-03-14 08:30:04 +00:00
for name , conf := range c . HTTP . Routers {
if _ , exists := configuration . HTTP . Routers [ name ] ; exists {
logger . WithField ( log . RouterName , name ) . Warn ( "HTTP router already configured, skipping" )
} else {
configuration . HTTP . Routers [ name ] = conf
}
}
for name , conf := range c . HTTP . Middlewares {
if _ , exists := configuration . HTTP . Middlewares [ name ] ; exists {
logger . WithField ( log . MiddlewareName , name ) . Warn ( "HTTP middleware already configured, skipping" )
} else {
configuration . HTTP . Middlewares [ name ] = conf
}
}
for name , conf := range c . HTTP . Services {
if _ , exists := configuration . HTTP . Services [ name ] ; exists {
logger . WithField ( log . ServiceName , name ) . Warn ( "HTTP service already configured, skipping" )
2017-05-26 13:32:03 +00:00
} else {
2019-03-14 08:30:04 +00:00
configuration . HTTP . Services [ name ] = conf
2017-05-26 13:32:03 +00:00
}
}
2019-03-14 08:30:04 +00:00
for name , conf := range c . TCP . Routers {
if _ , exists := configuration . TCP . Routers [ name ] ; exists {
logger . WithField ( log . RouterName , name ) . Warn ( "TCP router already configured, skipping" )
2017-05-26 13:32:03 +00:00
} else {
2019-03-14 08:30:04 +00:00
configuration . TCP . Routers [ name ] = conf
2018-11-14 09:18:03 +00:00
}
}
2019-03-14 08:30:04 +00:00
for name , conf := range c . TCP . Services {
if _ , exists := configuration . TCP . Services [ name ] ; exists {
logger . WithField ( log . ServiceName , name ) . Warn ( "TCP service already configured, skipping" )
2018-11-14 09:18:03 +00:00
} else {
2019-03-14 08:30:04 +00:00
configuration . TCP . Services [ name ] = conf
2017-05-26 13:32:03 +00:00
}
}
2020-02-11 00:26:04 +00:00
for name , conf := range c . UDP . Routers {
if _ , exists := configuration . UDP . Routers [ name ] ; exists {
logger . WithField ( log . RouterName , name ) . Warn ( "UDP router already configured, skipping" )
} else {
configuration . UDP . Routers [ name ] = conf
}
}
for name , conf := range c . UDP . Services {
if _ , exists := configuration . UDP . Services [ name ] ; exists {
logger . WithField ( log . ServiceName , name ) . Warn ( "UDP service already configured, skipping" )
} else {
configuration . UDP . Services [ name ] = conf
}
}
2019-06-27 21:58:03 +00:00
for _ , conf := range c . TLS . Certificates {
2017-11-09 11:16:03 +00:00
if _ , exists := configTLSMaps [ conf ] ; exists {
2019-03-14 08:30:04 +00:00
logger . Warnf ( "TLS configuration %v already configured, skipping" , conf )
2017-11-09 11:16:03 +00:00
} else {
configTLSMaps [ conf ] = struct { } { }
}
}
2019-07-18 14:26:05 +00:00
for name , conf := range c . TLS . Options {
if _ , exists := configuration . TLS . Options [ name ] ; exists {
logger . Warnf ( "TLS options %v already configured, skipping" , name )
} else {
if configuration . TLS . Options == nil {
configuration . TLS . Options = map [ string ] tls . Options { }
}
configuration . TLS . Options [ name ] = conf
}
}
for name , conf := range c . TLS . Stores {
if _ , exists := configuration . TLS . Stores [ name ] ; exists {
logger . Warnf ( "TLS store %v already configured, skipping" , name )
} else {
if configuration . TLS . Stores == nil {
configuration . TLS . Stores = map [ string ] tls . Store { }
}
configuration . TLS . Stores [ name ] = conf
}
}
2017-11-09 11:16:03 +00:00
}
2018-11-14 09:18:03 +00:00
2019-07-18 14:26:05 +00:00
if len ( configTLSMaps ) > 0 && configuration . TLS == nil {
2019-07-10 07:26:04 +00:00
configuration . TLS = & dynamic . TLSConfiguration { }
2019-06-27 21:58:03 +00:00
}
2017-11-09 11:16:03 +00:00
for conf := range configTLSMaps {
2019-06-27 21:58:03 +00:00
configuration . TLS . Certificates = append ( configuration . TLS . Certificates , conf )
2017-11-09 11:16:03 +00:00
}
2019-06-27 21:58:03 +00:00
2017-05-26 13:32:03 +00:00
return configuration , nil
}
2019-03-27 14:02:06 +00:00
// CreateConfiguration creates a provider configuration from content using templating.
2019-09-13 17:28:04 +00:00
func ( p * Provider ) CreateConfiguration ( ctx context . Context , filename string , funcMap template . FuncMap , templateObjects interface { } ) ( * dynamic . Configuration , error ) {
2019-06-26 16:18:04 +00:00
tmplContent , err := readFile ( filename )
if err != nil {
2020-05-11 10:06:07 +00:00
return nil , fmt . Errorf ( "error reading configuration file: %s - %w" , filename , err )
2019-06-26 16:18:04 +00:00
}
2020-07-07 12:42:03 +00:00
defaultFuncMap := sprig . TxtFuncMap ( )
2019-03-27 14:02:06 +00:00
defaultFuncMap [ "normalize" ] = provider . Normalize
defaultFuncMap [ "split" ] = strings . Split
for funcID , funcElement := range funcMap {
defaultFuncMap [ funcID ] = funcElement
}
tmpl := template . New ( p . Filename ) . Funcs ( defaultFuncMap )
2019-06-26 16:18:04 +00:00
_ , err = tmpl . Parse ( tmplContent )
2019-03-27 14:02:06 +00:00
if err != nil {
return nil , err
}
var buffer bytes . Buffer
err = tmpl . Execute ( & buffer , templateObjects )
if err != nil {
return nil , err
}
2020-07-07 12:42:03 +00:00
renderedTemplate := buffer . String ( )
2019-03-27 14:02:06 +00:00
if p . DebugLogGeneratedTemplate {
2019-09-13 17:28:04 +00:00
logger := log . FromContext ( ctx )
2019-06-26 16:18:04 +00:00
logger . Debugf ( "Template content: %s" , tmplContent )
logger . Debugf ( "Rendering results: %s" , renderedTemplate )
2019-03-27 14:02:06 +00:00
}
2019-06-26 16:18:04 +00:00
return p . decodeConfiguration ( filename , renderedTemplate )
2019-03-27 14:02:06 +00:00
}
// DecodeConfiguration Decodes a *types.Configuration from a content.
2019-07-10 07:26:04 +00:00
func ( p * Provider ) DecodeConfiguration ( filename string ) ( * dynamic . Configuration , error ) {
2019-06-26 16:18:04 +00:00
content , err := readFile ( filename )
if err != nil {
2020-05-11 10:06:07 +00:00
return nil , fmt . Errorf ( "error reading configuration file: %s - %w" , filename , err )
2019-06-26 16:18:04 +00:00
}
return p . decodeConfiguration ( filename , content )
}
2020-07-07 12:42:03 +00:00
func ( p * Provider ) decodeConfiguration ( filePath , content string ) ( * dynamic . Configuration , error ) {
2019-07-10 07:26:04 +00:00
configuration := & dynamic . Configuration {
HTTP : & dynamic . HTTPConfiguration {
Routers : make ( map [ string ] * dynamic . Router ) ,
Middlewares : make ( map [ string ] * dynamic . Middleware ) ,
Services : make ( map [ string ] * dynamic . Service ) ,
2019-03-27 14:02:06 +00:00
} ,
2019-07-10 07:26:04 +00:00
TCP : & dynamic . TCPConfiguration {
Routers : make ( map [ string ] * dynamic . TCPRouter ) ,
Services : make ( map [ string ] * dynamic . TCPService ) ,
2019-03-27 14:02:06 +00:00
} ,
2019-07-10 07:26:04 +00:00
TLS : & dynamic . TLSConfiguration {
2019-06-27 21:58:03 +00:00
Stores : make ( map [ string ] tls . Store ) ,
Options : make ( map [ string ] tls . Options ) ,
} ,
2020-02-11 00:26:04 +00:00
UDP : & dynamic . UDPConfiguration {
Routers : make ( map [ string ] * dynamic . UDPRouter ) ,
Services : make ( map [ string ] * dynamic . UDPService ) ,
} ,
2019-03-27 14:02:06 +00:00
}
2019-06-26 16:18:04 +00:00
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 )
2019-03-27 14:02:06 +00:00
}
2019-06-26 16:18:04 +00:00
2019-03-27 14:02:06 +00:00
return configuration , nil
}
2019-06-26 16:18:04 +00:00
func readFile ( filename string ) ( string , error ) {
if len ( filename ) > 0 {
buf , err := ioutil . ReadFile ( filename )
if err != nil {
return "" , err
}
return string ( buf ) , nil
}
return "" , fmt . Errorf ( "invalid filename: %s" , filename )
}