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