package main import ( "errors" "fmt" fmtlog "log" "regexp" "strings" "time" "github.com/containous/traefik/acme" "github.com/containous/traefik/provider" "github.com/containous/traefik/types" "github.com/mitchellh/mapstructure" "github.com/spf13/viper" ) // GlobalConfiguration holds global configuration (with providers, etc.). // It's populated from the traefik configuration file passed as an argument to the binary. type GlobalConfiguration struct { GraceTimeOut int64 Debug bool AccessLogsFile string TraefikLogsFile string LogLevel string EntryPoints EntryPoints ACME *acme.ACME DefaultEntryPoints DefaultEntryPoints ProvidersThrottleDuration time.Duration MaxIdleConnsPerHost int Retry *Retry Docker *provider.Docker File *provider.File Web *WebProvider Marathon *provider.Marathon Consul *provider.Consul ConsulCatalog *provider.ConsulCatalog Etcd *provider.Etcd Zookeeper *provider.Zookepper Boltdb *provider.BoltDb Kubernetes *provider.Kubernetes } // DefaultEntryPoints holds default entry points type DefaultEntryPoints []string // String is the method to format the flag's value, part of the flag.Value interface. // The String method's output will be used in diagnostics. func (dep *DefaultEntryPoints) String() string { return fmt.Sprintf("%#v", dep) } // Set is the method to set the flag value, part of the flag.Value interface. // Set's argument is a string to be parsed to set the flag. // It's a comma-separated list, so we split it. func (dep *DefaultEntryPoints) Set(value string) error { entrypoints := strings.Split(value, ",") if len(entrypoints) == 0 { return errors.New("Bad DefaultEntryPoints format: " + value) } for _, entrypoint := range entrypoints { *dep = append(*dep, entrypoint) } return nil } // Type is type of the struct func (dep *DefaultEntryPoints) Type() string { return fmt.Sprint("defaultentrypoints²") } // EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...) type EntryPoints map[string]*EntryPoint // String is the method to format the flag's value, part of the flag.Value interface. // The String method's output will be used in diagnostics. func (ep *EntryPoints) String() string { return "" } // Set is the method to set the flag value, part of the flag.Value interface. // Set's argument is a string to be parsed to set the flag. // It's a comma-separated list, so we split it. func (ep *EntryPoints) Set(value string) error { regex := regexp.MustCompile("(?:Name:(?P\\S*))\\s*(?:Address:(?P
\\S*))?\\s*(?:TLS:(?P\\S*))?\\s*(?:Redirect.EntryPoint:(?P\\S*))?\\s*(?:Redirect.Regex:(?P\\S*))?\\s*(?:Redirect.Replacement:(?P\\S*))?") match := regex.FindAllStringSubmatch(value, -1) if match == nil { return errors.New("Bad EntryPoints format: " + value) } matchResult := match[0] result := make(map[string]string) for i, name := range regex.SubexpNames() { if i != 0 { result[name] = matchResult[i] } } var tls *TLS if len(result["TLS"]) > 0 { certs := Certificates{} if err := certs.Set(result["TLS"]); err != nil { return err } tls = &TLS{ Certificates: certs, } } var redirect *Redirect if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 { redirect = &Redirect{ EntryPoint: result["RedirectEntryPoint"], Regex: result["RedirectRegex"], Replacement: result["RedirectReplacement"], } } (*ep)[result["Name"]] = &EntryPoint{ Address: result["Address"], TLS: tls, Redirect: redirect, } return nil } // Type is type of the struct func (ep *EntryPoints) Type() string { return fmt.Sprint("entrypoints²") } // EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) type EntryPoint struct { Network string Address string TLS *TLS Redirect *Redirect } // Redirect configures a redirection of an entry point to another, or to an URL type Redirect struct { EntryPoint string Regex string Replacement string } // TLS configures TLS for an entry point type TLS struct { Certificates Certificates } // Certificates defines traefik certificates type type Certificates []Certificate // String is the method to format the flag's value, part of the flag.Value interface. // The String method's output will be used in diagnostics. func (certs *Certificates) String() string { if len(*certs) == 0 { return "" } return (*certs)[0].CertFile + "," + (*certs)[0].KeyFile } // Set is the method to set the flag value, part of the flag.Value interface. // Set's argument is a string to be parsed to set the flag. // It's a comma-separated list, so we split it. func (certs *Certificates) Set(value string) error { files := strings.Split(value, ",") if len(files) != 2 { return errors.New("Bad certificates format: " + value) } *certs = append(*certs, Certificate{ CertFile: files[0], KeyFile: files[1], }) return nil } // Type is type of the struct func (certs *Certificates) Type() string { return fmt.Sprint("certificates") } // Certificate holds a SSL cert/key pair type Certificate struct { CertFile string KeyFile string } // Retry contains request retry config type Retry struct { Attempts int MaxMem int64 } // NewGlobalConfiguration returns a GlobalConfiguration with default values. func NewGlobalConfiguration() *GlobalConfiguration { return new(GlobalConfiguration) } // LoadConfiguration returns a GlobalConfiguration. func LoadConfiguration() *GlobalConfiguration { configuration := NewGlobalConfiguration() viper.SetEnvPrefix("traefik") viper.SetConfigType("toml") viper.AutomaticEnv() if len(viper.GetString("configFile")) > 0 { viper.SetConfigFile(viper.GetString("configFile")) } else { viper.SetConfigName("traefik") // name of config file (without extension) } viper.AddConfigPath("/etc/traefik/") // path to look for the config file in viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths viper.AddConfigPath(".") // optionally look for config in the working directory if err := viper.ReadInConfig(); err != nil { if len(viper.ConfigFileUsed()) > 0 { fmtlog.Printf("Error reading configuration file: %s", err) } else { fmtlog.Printf("No configuration file found") } } if len(arguments.EntryPoints) > 0 { viper.Set("entryPoints", arguments.EntryPoints) } if len(arguments.DefaultEntryPoints) > 0 { viper.Set("defaultEntryPoints", arguments.DefaultEntryPoints) } if arguments.web { viper.Set("web", arguments.Web) } if arguments.file { viper.Set("file", arguments.File) } if !arguments.dockerTLS { arguments.Docker.TLS = nil } if arguments.docker { viper.Set("docker", arguments.Docker) } if arguments.marathon { viper.Set("marathon", arguments.Marathon) } if !arguments.consulTLS { arguments.Consul.TLS = nil } if arguments.consul { viper.Set("consul", arguments.Consul) } if arguments.consulCatalog { viper.Set("consulCatalog", arguments.ConsulCatalog) } if arguments.zookeeper { viper.Set("zookeeper", arguments.Zookeeper) } if !arguments.etcdTLS { arguments.Etcd.TLS = nil } if arguments.etcd { viper.Set("etcd", arguments.Etcd) } if arguments.boltdb { viper.Set("boltdb", arguments.Boltdb) } if arguments.kubernetes { viper.Set("kubernetes", arguments.Kubernetes) } if err := unmarshal(&configuration); err != nil { fmtlog.Fatalf("Error reading file: %s", err) } if len(configuration.EntryPoints) == 0 { configuration.EntryPoints = make(map[string]*EntryPoint) configuration.EntryPoints["http"] = &EntryPoint{ Address: ":80", } configuration.DefaultEntryPoints = []string{"http"} } if configuration.File != nil && len(configuration.File.Filename) == 0 { // no filename, setting to global config file configuration.File.Filename = viper.ConfigFileUsed() } return configuration } func unmarshal(rawVal interface{}) error { config := &mapstructure.DecoderConfig{ DecodeHook: mapstructure.StringToTimeDurationHookFunc(), Metadata: nil, Result: rawVal, WeaklyTypedInput: true, } decoder, err := mapstructure.NewDecoder(config) if err != nil { return err } err = decoder.Decode(viper.AllSettings()) if err != nil { return err } return nil } type configs map[string]*types.Configuration