diff --git a/configuration.go b/configuration.go index a055bba0f..21e95d475 100644 --- a/configuration.go +++ b/configuration.go @@ -13,8 +13,8 @@ import ( // TraefikConfiguration holds GlobalConfiguration and other stuff type TraefikConfiguration struct { - GlobalConfiguration - ConfigFile string `short:"c" description:"Configuration file to use (TOML)."` + GlobalConfiguration `mapstructure:",squash"` + ConfigFile string `short:"c" description:"Configuration file to use (TOML)."` } // GlobalConfiguration holds global configuration (with providers, etc.). diff --git a/glide.lock b/glide.lock index e7694a0e2..d2ba4c3c5 100644 --- a/glide.lock +++ b/glide.lock @@ -22,13 +22,13 @@ imports: - name: github.com/containous/mux version: a819b77bba13f0c0cbe36e437bc2e948411b3996 - name: github.com/containous/staert - version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602 + version: 436a2f21e5e28c7c761130720befe05381db0765 - name: github.com/coreos/etcd version: c400d05d0aa73e21e431c16145e558d624098018 subpackages: + - client - Godeps/_workspace/src/github.com/ugorji/go/codec - Godeps/_workspace/src/golang.org/x/net/context - - client - pkg/pathutil - pkg/types - name: github.com/davecgh/go-spew @@ -134,7 +134,9 @@ imports: - name: github.com/Microsoft/go-winio version: ce2922f643c8fd76b46cadc7f404a06282678b34 - name: github.com/miekg/dns - version: 5d001d020961ae1c184f9f8152fdc73810481677 + version: 48ab6605c66ac797e07f615101c3e9e10e932b66 +- name: github.com/mitchellh/mapstructure + version: d2dd0262208475919e1a362f675cfc0e7c10e905 - name: github.com/moul/http2curl version: b1479103caacaa39319f75e7f57fc545287fca0d - name: github.com/ogier/pflag diff --git a/glide.yaml b/glide.yaml index 36e73fa4b..5235bcb23 100644 --- a/glide.yaml +++ b/glide.yaml @@ -21,7 +21,7 @@ import: - stream - utils - package: github.com/containous/staert - version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602 + version: 436a2f21e5e28c7c761130720befe05381db0765 - package: github.com/docker/engine-api version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb subpackages: diff --git a/integration/consul_test.go b/integration/consul_test.go index 0c2c97058..9002a778c 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -24,7 +24,7 @@ type ConsulSuite struct { kv store.Store } -func (s *ConsulSuite) SetUpSuite(c *check.C) { +func (s *ConsulSuite) SetUpTest(c *check.C) { s.createComposeProject(c, "consul") s.composeProject.Start(c) @@ -52,6 +52,15 @@ func (s *ConsulSuite) SetUpSuite(c *check.C) { c.Assert(err, checker.IsNil) } +func (s *ConsulSuite) TearDownTest(c *check.C) { + // shutdown and delete compose project + if s.composeProject != nil { + s.composeProject.Stop(c) + } +} + +func (s *ConsulSuite) TearDownSuite(c *check.C) {} + func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost}) @@ -190,3 +199,109 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(resp.StatusCode, checker.Equals, 404) } + +func (s *ConsulSuite) TestGlobalConfiguration(c *check.C) { + consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + err := s.kv.Put("traefik/entrypoints/http/address", []byte(":8001"), nil) + c.Assert(err, checker.IsNil) + + // wait for consul + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("traefik/entrypoints/http/address") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + // start traefik + cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--consul", "--consul.endpoint="+consulHost+":8500") + // cmd.Stdout = os.Stdout + // cmd.Stderr = os.Stderr + + err = cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + whoami1 := s.composeProject.Container(c, "whoami1") + whoami2 := s.composeProject.Container(c, "whoami2") + whoami3 := s.composeProject.Container(c, "whoami3") + whoami4 := s.composeProject.Container(c, "whoami4") + + backend1 := map[string]string{ + "traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5", + "traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80", + "traefik/backends/backend1/servers/server1/weight": "10", + "traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80", + "traefik/backends/backend1/servers/server2/weight": "1", + } + backend2 := map[string]string{ + "traefik/backends/backend2/loadbalancer/method": "drr", + "traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80", + "traefik/backends/backend2/servers/server1/weight": "1", + "traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80", + "traefik/backends/backend2/servers/server2/weight": "2", + } + frontend1 := map[string]string{ + "traefik/frontends/frontend1/backend": "backend2", + "traefik/frontends/frontend1/entrypoints": "http", + "traefik/frontends/frontend1/priority": "1", + "traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost", + } + frontend2 := map[string]string{ + "traefik/frontends/frontend2/backend": "backend1", + "traefik/frontends/frontend2/entrypoints": "http", + "traefik/frontends/frontend2/priority": "10", + "traefik/frontends/frontend2/routes/test_2/rule": "Path:/test", + } + for key, value := range backend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range backend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + + // wait for consul + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("traefik/frontends/frontend2/routes/test_2/rule") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + // wait for traefik + err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error { + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if !strings.Contains(string(body), "Path:/test") { + return errors.New("Incorrect traefik config") + } + return nil + }) + c.Assert(err, checker.IsNil) + + //check + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8001/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.localhost" + response, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(response.StatusCode, checker.Equals, 200) +} diff --git a/integration/etcd_test.go b/integration/etcd_test.go index 316d989b3..09a329e37 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -25,7 +25,7 @@ type EtcdSuite struct { kv store.Store } -func (s *EtcdSuite) SetUpSuite(c *check.C) { +func (s *EtcdSuite) SetUpTest(c *check.C) { s.createComposeProject(c, "etcd") s.composeProject.Start(c) @@ -54,6 +54,15 @@ func (s *EtcdSuite) SetUpSuite(c *check.C) { c.Assert(err, checker.IsNil) } +func (s *EtcdSuite) TearDownTest(c *check.C) { + // shutdown and delete compose project + if s.composeProject != nil { + s.composeProject.Stop(c) + } +} + +func (s *EtcdSuite) TearDownSuite(c *check.C) {} + func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) { etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost}) @@ -193,3 +202,109 @@ func (s *EtcdSuite) TestNominalConfiguration(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(resp.StatusCode, checker.Equals, 404) } + +func (s *EtcdSuite) TestGlobalConfiguration(c *check.C) { + etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + err := s.kv.Put("/traefik/entrypoints/http/address", []byte(":8001"), nil) + c.Assert(err, checker.IsNil) + + // wait for etcd + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("/traefik/entrypoints/http/address") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + // start traefik + cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--etcd", "--etcd.endpoint="+etcdHost+":4001") + // cmd.Stdout = os.Stdout + // cmd.Stderr = os.Stderr + + err = cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + whoami1 := s.composeProject.Container(c, "whoami1") + whoami2 := s.composeProject.Container(c, "whoami2") + whoami3 := s.composeProject.Container(c, "whoami3") + whoami4 := s.composeProject.Container(c, "whoami4") + + backend1 := map[string]string{ + "/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5", + "/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend1/servers/server1/weight": "10", + "/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend1/servers/server2/weight": "1", + } + backend2 := map[string]string{ + "/traefik/backends/backend2/loadbalancer/method": "drr", + "/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend2/servers/server1/weight": "1", + "/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend2/servers/server2/weight": "2", + } + frontend1 := map[string]string{ + "/traefik/frontends/frontend1/backend": "backend2", + "/traefik/frontends/frontend1/entrypoints": "http", + "/traefik/frontends/frontend1/priority": "1", + "/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost", + } + frontend2 := map[string]string{ + "/traefik/frontends/frontend2/backend": "backend1", + "/traefik/frontends/frontend2/entrypoints": "http", + "/traefik/frontends/frontend2/priority": "10", + "/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test", + } + for key, value := range backend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range backend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + + // wait for etcd + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + // wait for traefik + err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error { + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if !strings.Contains(string(body), "Path:/test") { + return errors.New("Incorrect traefik config") + } + return nil + }) + c.Assert(err, checker.IsNil) + + //check + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8001/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.localhost" + response, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(response.StatusCode, checker.Equals, 200) +} diff --git a/provider/boltdb.go b/provider/boltdb.go index 574956ace..285206955 100644 --- a/provider/boltdb.go +++ b/provider/boltdb.go @@ -9,7 +9,7 @@ import ( // BoltDb holds configurations of the BoltDb provider. type BoltDb struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/consul.go b/provider/consul.go index f936e79f3..b8967e61c 100644 --- a/provider/consul.go +++ b/provider/consul.go @@ -9,7 +9,7 @@ import ( // Consul holds configurations of the Consul provider. type Consul struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/consul_catalog.go b/provider/consul_catalog.go index 8622b66e0..4ff332517 100644 --- a/provider/consul_catalog.go +++ b/provider/consul_catalog.go @@ -25,11 +25,11 @@ const ( // ConsulCatalog holds configurations of the Consul catalog provider. type ConsulCatalog struct { - BaseProvider - Endpoint string `description:"Consul server endpoint"` - Domain string `description:"Default domain used"` - client *api.Client - Prefix string + BaseProvider `mapstructure:",squash"` + Endpoint string `description:"Consul server endpoint"` + Domain string `description:"Default domain used"` + client *api.Client + Prefix string } type serviceUpdate struct { diff --git a/provider/docker.go b/provider/docker.go index f71aa8892..a8655c8ed 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -29,7 +29,7 @@ const DockerAPIVersion string = "1.21" // Docker holds configurations of the Docker provider. type Docker struct { - BaseProvider + BaseProvider `mapstructure:",squash"` Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"` Domain string `description:"Default domain used"` TLS *DockerTLS `description:"Enable Docker TLS support"` diff --git a/provider/etcd.go b/provider/etcd.go index 934e0f245..6c41757fa 100644 --- a/provider/etcd.go +++ b/provider/etcd.go @@ -9,7 +9,7 @@ import ( // Etcd holds configurations of the Etcd provider. type Etcd struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/file.go b/provider/file.go index 07bcbd02f..05cb89eee 100644 --- a/provider/file.go +++ b/provider/file.go @@ -14,7 +14,7 @@ import ( // File holds configurations of the File provider. type File struct { - BaseProvider + BaseProvider `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 0535b9570..b4de0a1fd 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -51,7 +51,7 @@ func (ns *Namespaces) SetValue(val interface{}) { // Kubernetes holds configurations of the Kubernetes provider. type Kubernetes struct { - BaseProvider + BaseProvider `mapstructure:",squash"` Endpoint string `description:"Kubernetes server endpoint"` DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"` Namespaces Namespaces `description:"Kubernetes namespaces"` diff --git a/provider/kv.go b/provider/kv.go index 728e8a2f3..3b7f6418c 100644 --- a/provider/kv.go +++ b/provider/kv.go @@ -22,12 +22,12 @@ import ( // Kv holds common configurations of key-value providers. type Kv struct { - BaseProvider - Endpoint string `description:"Comma sepparated server endpoints"` - Prefix string `description:"Prefix used for KV store"` - TLS *KvTLS `description:"Enable TLS support"` - storeType store.Backend - kvclient store.Store + BaseProvider `mapstructure:",squash"` + Endpoint string `description:"Comma sepparated server endpoints"` + Prefix string `description:"Prefix used for KV store"` + TLS *KvTLS `description:"Enable TLS support"` + storeType store.Backend + kvclient store.Store } // KvTLS holds TLS specific configurations diff --git a/provider/zk.go b/provider/zk.go index 06eb65000..416d62844 100644 --- a/provider/zk.go +++ b/provider/zk.go @@ -9,7 +9,7 @@ import ( // Zookepper holds configurations of the Zookepper provider. type Zookepper struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/traefik.go b/traefik.go index a471796ff..89c9960fb 100644 --- a/traefik.go +++ b/traefik.go @@ -10,6 +10,11 @@ import ( "github.com/containous/traefik/middlewares" "github.com/containous/traefik/provider" "github.com/containous/traefik/types" + "github.com/docker/libkv/store" + "github.com/docker/libkv/store/boltdb" + "github.com/docker/libkv/store/consul" + "github.com/docker/libkv/store/etcd" + "github.com/docker/libkv/store/zookeeper" fmtlog "log" "net/http" "os" @@ -111,6 +116,73 @@ Complete documentation is available at https://traefik.io`, traefikConfiguration.ConfigFile = toml.ConfigFileUsed() + var kv *staert.KvSource + var err error + if traefikConfiguration.Consul != nil { + //init KvSource + consul.Register() + kv, err = staert.NewKvSource( + store.CONSUL, + strings.Split(traefikConfiguration.Consul.Endpoint, ","), + nil, + strings.TrimPrefix(traefikConfiguration.Consul.Prefix, "/"), // TrimPrefix should be done in https://github.com/docker/libkv/blob/master/store/consul/consul.go#L113 : IDK why it doen't work + ) + } else if traefikConfiguration.Etcd != nil { + //init KvSource + etcd.Register() + kv, err = staert.NewKvSource( + store.ETCD, + strings.Split(traefikConfiguration.Etcd.Endpoint, ","), + nil, + traefikConfiguration.Etcd.Prefix, + ) + } else if traefikConfiguration.Zookeeper != nil { + //init KvSource + zookeeper.Register() + kv, err = staert.NewKvSource( + store.ZK, + strings.Split(traefikConfiguration.Zookeeper.Endpoint, ","), + nil, + traefikConfiguration.Zookeeper.Prefix, + ) + } else if traefikConfiguration.Boltdb != nil { + //init KvSource + boltdb.Register() + kv, err = staert.NewKvSource( + store.BOLTDB, + strings.Split(traefikConfiguration.Boltdb.Endpoint, ","), + nil, + traefikConfiguration.Boltdb.Prefix, + ) + } + if err != nil { + fmtlog.Println(err) + os.Exit(-1) + } + + // TO DELETE : Used once to fill the kv store + // if kv != nil { + // fmtlog.Println("Try to store global configuration in consul store") + // if err := kv.StoreConfig(traefikConfiguration); err != nil { + // fmtlog.Println(fmt.Errorf("Error : %s", err)) + // os.Exit(-1) + // } else { + // fmtlog.Println("It seems okay :)") + // jsonConf, _ := json.Marshal(traefikConfiguration) + // fmtlog.Printf("Global configuration loaded %s", string(jsonConf)) + // os.Exit(0) + // } + // } + + //TODO : log warning if many KvStore or set priority + if kv != nil { + fmtlog.Println("KV Store found") + s.AddSource(kv) + if _, err := s.LoadConfig(); err != nil { + fmtlog.Println(fmt.Errorf("Error : %s", err)) + } + } + if err := s.Run(); err != nil { fmtlog.Println(err) os.Exit(-1)