2017-04-17 12:50:02 +02:00
package file
2015-09-07 15:25:13 +02:00
import (
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
2016-08-18 14:20:11 +02:00
"github.com/containous/traefik/log"
2017-04-17 12:50:02 +02:00
"github.com/containous/traefik/provider"
2016-03-31 18:57:08 +02:00
"github.com/containous/traefik/safe"
2017-11-09 12:16:03 +01:00
"github.com/containous/traefik/tls"
2016-02-24 16:43:39 +01:00
"github.com/containous/traefik/types"
2015-09-24 17:16:13 +02:00
"gopkg.in/fsnotify.v1"
2015-09-07 15:25:13 +02:00
)
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 {
2017-10-02 10:32:02 +02:00
provider . BaseProvider ` mapstructure:",squash" export:"true" `
Directory string ` description:"Load configuration from one or more .toml files in a directory" export:"true" `
2015-09-07 15:25:13 +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.
2017-04-17 12:50:02 +02:00
func ( p * Provider ) Provide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool , constraints types . Constraints ) 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
if p . Directory != "" {
watchItem = p . Directory
} else {
2017-11-09 12:16:03 +01:00
watchItem = filepath . Dir ( p . Filename )
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
2017-12-02 19:25:29 +01:00
func ( p * Provider ) BuildConfiguration ( ) ( * types . Configuration , error ) {
2017-11-21 10:24:03 +01:00
if p . Directory != "" {
2018-03-22 11:14:04 -04:00
return p . loadFileConfigFromDirectory ( p . Directory , nil )
2017-11-21 10:24:03 +01:00
}
2018-03-22 11:14:04 -04:00
return p . loadFileConfig ( p . Filename )
2017-11-21 10:24:03 +01:00
}
2017-05-26 14:32:03 +01:00
func ( p * Provider ) addWatcher ( pool * safe . Pool , directory string , configurationChan chan <- types . ConfigMessage , callback func ( chan <- types . ConfigMessage , fsnotify . Event ) ) error {
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
}
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 == "" {
_ , evtFileName := filepath . Split ( evt . Name )
_ , confFileName := filepath . Split ( p . Filename )
if evtFileName == confFileName {
callback ( configurationChan , evt )
}
} else {
callback ( configurationChan , evt )
}
2017-05-26 14:32:03 +01:00
case err := <- watcher . Errors :
log . 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
} )
err = watcher . Add ( directory )
if err != nil {
return fmt . Errorf ( "error adding file watcher: %s" , err )
2015-09-07 17:39:22 +02:00
}
2015-09-07 15:25:13 +02:00
2017-05-26 14:32:03 +01:00
return nil
}
2017-11-21 10:24:03 +01:00
func ( p * Provider ) watcherCallback ( configurationChan chan <- types . ConfigMessage , event fsnotify . Event ) {
watchItem := p . Filename
if p . Directory != "" {
watchItem = p . Directory
}
if _ , err := os . Stat ( watchItem ) ; err != nil {
log . Debugf ( "Unable to watch %s : %v" , watchItem , err )
return
}
2017-12-02 19:25:29 +01:00
configuration , err := p . BuildConfiguration ( )
2017-11-21 10:24:03 +01:00
if err != nil {
log . Errorf ( "Error occurred during watcher callback: %s" , err )
return
}
sendConfigToChannel ( configurationChan , configuration )
}
2017-05-26 14:32:03 +01:00
func sendConfigToChannel ( configurationChan chan <- types . ConfigMessage , configuration * types . Configuration ) {
2015-11-13 11:50:32 +01:00
configurationChan <- types . ConfigMessage {
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 )
}
func ( p * Provider ) loadFileConfig ( filename string ) ( * types . Configuration , error ) {
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-03-22 11:14:04 -04:00
configuration , err := p . CreateConfiguration ( fileContent , template . FuncMap { } , false )
if err != nil {
return nil , err
}
if configuration == nil || configuration . Backends == nil && configuration . Frontends == nil && configuration . TLS == nil {
configuration = & types . Configuration {
Frontends : make ( map [ string ] * types . Frontend ) ,
Backends : make ( map [ string ] * types . Backend ) ,
}
}
return configuration , err
2017-05-26 14:32:03 +01:00
}
2018-03-22 11:14:04 -04:00
func ( p * Provider ) loadFileConfigFromDirectory ( directory string , configuration * types . Configuration ) ( * types . Configuration , error ) {
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 {
configuration = & types . Configuration {
2017-12-21 21:24:03 +01:00
Frontends : make ( map [ string ] * types . Frontend ) ,
Backends : make ( map [ string ] * types . Backend ) ,
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-03-22 11:14:04 -04:00
configuration , err = p . loadFileConfigFromDirectory ( 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
}
var c * types . Configuration
2018-03-22 11:14:04 -04:00
c , err = p . loadFileConfig ( path . Join ( directory , item . Name ( ) ) )
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
}
for backendName , backend := range c . Backends {
if _ , exists := configuration . Backends [ backendName ] ; exists {
log . Warnf ( "Backend %s already configured, skipping" , backendName )
} else {
configuration . Backends [ backendName ] = backend
}
}
for frontendName , frontend := range c . Frontends {
if _ , exists := configuration . Frontends [ frontendName ] ; exists {
log . Warnf ( "Frontend %s already configured, skipping" , frontendName )
} else {
configuration . Frontends [ frontendName ] = frontend
}
}
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 {
log . Warnf ( "TLS Configuration %v already configured, skipping" , conf )
} else {
configTLSMaps [ conf ] = struct { } { }
}
}
}
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
}