From 7ddefcef72066bfd0eed58c2badf3cd25649f8ac Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Tue, 21 Nov 2017 10:24:03 +0100 Subject: [PATCH] Add file to storeconfig --- cmd/traefik/storeconfig.go | 35 +++++++++++++++++++-- docs/user-guide/kv-config.md | 3 ++ integration/consul_test.go | 56 ++++++++++++++++++++++++++++++--- provider/file/file.go | 61 ++++++++++++++++++------------------ 4 files changed, 119 insertions(+), 36 deletions(-) diff --git a/cmd/traefik/storeconfig.go b/cmd/traefik/storeconfig.go index 9cffc87c1..534a0682d 100644 --- a/cmd/traefik/storeconfig.go +++ b/cmd/traefik/storeconfig.go @@ -3,7 +3,7 @@ package main import ( "encoding/json" "fmt" - fmtlog "log" + stdlog "log" "github.com/containous/flaeg" "github.com/containous/staert" @@ -29,15 +29,44 @@ func runStoreConfig(kv *staert.KvSource, traefikConfiguration *TraefikConfigurat if kv == nil { return fmt.Errorf("error using command storeconfig, no Key-value store defined") } + + fileConfig := traefikConfiguration.GlobalConfiguration.File + if fileConfig != nil { + traefikConfiguration.GlobalConfiguration.File = nil + if len(fileConfig.Filename) == 0 && len(fileConfig.Directory) == 0 { + fileConfig.Filename = traefikConfiguration.ConfigFile + } + } + jsonConf, err := json.Marshal(traefikConfiguration.GlobalConfiguration) if err != nil { return err } - fmtlog.Printf("Storing configuration: %s\n", jsonConf) + stdlog.Printf("Storing configuration: %s\n", jsonConf) + err = kv.StoreConfig(traefikConfiguration.GlobalConfiguration) if err != nil { return err } + + if fileConfig != nil { + jsonConf, err = json.Marshal(fileConfig) + if err != nil { + return err + } + + stdlog.Printf("Storing file configuration: %s\n", jsonConf) + config, err := fileConfig.LoadConfig() + if err != nil { + return err + } + + stdlog.Print("Writing config to KV") + err = kv.StoreConfig(config) + if err != nil { + return err + } + } if traefikConfiguration.GlobalConfiguration.ACME != nil && len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 { // convert ACME json file to KV store localStore := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) @@ -45,11 +74,13 @@ func runStoreConfig(kv *staert.KvSource, traefikConfiguration *TraefikConfigurat if err != nil { return err } + meta := cluster.NewMetadata(object) err = meta.Marshall() if err != nil { return err } + source := staert.KvSource{ Store: kv, Prefix: traefikConfiguration.GlobalConfiguration.ACME.Storage, diff --git a/docs/user-guide/kv-config.md b/docs/user-guide/kv-config.md index 419584b9a..637f795b2 100644 --- a/docs/user-guide/kv-config.md +++ b/docs/user-guide/kv-config.md @@ -378,8 +378,11 @@ traefik storeconfig [flags] ... ``` This command is here only to automate the [process which upload the configuration into the Key-value store](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store). Træfik will not start but the [static configuration](/basics/#static-trfk-configuration) will be uploaded into the Key-value store. + If you configured ACME (Let's Encrypt), your registration account and your certificates will also be uploaded. +If you configured a file backend `[file]`, all your dynamic configuration (backends, frontends...) will be uploaded to the Key-value store. + To upload your ACME certificates to the KV store, get your Traefik TOML file and add the new `storage` option in the `acme` section: ```toml diff --git a/integration/consul_test.go b/integration/consul_test.go index 066d85dad..27b26e149 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -332,10 +332,10 @@ func (s *ConsulSuite) TestCommandStoreConfig(c *check.C) { c.Assert(err, checker.IsNil) // wait for traefik finish without error - cmd.Wait() + err = cmd.Wait() + c.Assert(err, checker.IsNil) - //CHECK - checkmap := map[string]string{ + expectedData := map[string]string{ "/traefik/loglevel": "DEBUG", "/traefik/defaultentrypoints/0": "http", "/traefik/entrypoints/http/address": ":8000", @@ -343,7 +343,7 @@ func (s *ConsulSuite) TestCommandStoreConfig(c *check.C) { "/traefik/consul/endpoint": consulHost + ":8500", } - for key, value := range checkmap { + for key, value := range expectedData { var p *store.KVPair err = try.Do(60*time.Second, func() error { p, err = s.kv.Get(key, nil) @@ -355,6 +355,54 @@ func (s *ConsulSuite) TestCommandStoreConfig(c *check.C) { } } +func (s *ConsulSuite) TestCommandStoreConfigWithFile(c *check.C) { + s.setupConsul(c) + consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + + cmd, display := s.traefikCmd( + "storeconfig", + withConfigFile("fixtures/simple_default.toml"), + "--consul.endpoint="+consulHost+":8500", + "--file.filename=fixtures/file/dir/simple1.toml") + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + + // wait for traefik finish without error + err = cmd.Wait() + c.Assert(err, checker.IsNil) + + expectedData := map[string]string{ + "/traefik/backends/backend1/servers/server1/url": "http://172.17.0.2:80", + "/traefik/frontends/frontend1/backend": "backend1", + "/traefik/frontends/frontend1/routes/test_1/rule": "Path:/test1", + } + + for key, value := range expectedData { + var p *store.KVPair + err = try.Do(10*time.Second, func() error { + p, err = s.kv.Get(key, nil) + return err + }) + c.Assert(err, checker.IsNil) + c.Assert(string(p.Value), checker.Equals, value) + } + + checkNotExistsMap := []string{ + "/traefik/file", + } + + for _, value := range checkNotExistsMap { + err = try.Do(10*time.Second, func() error { + if exists, err := s.kv.Exists(value, nil); err == nil && exists { + return fmt.Errorf("%s key is not suppose to exist in KV", value) + } + return nil + }) + c.Assert(err, checker.IsNil) + } +} + type TestStruct struct { String string Int int diff --git a/provider/file/file.go b/provider/file/file.go index 6744cf489..918c7cf4d 100644 --- a/provider/file/file.go +++ b/provider/file/file.go @@ -28,7 +28,7 @@ type Provider struct { // Provide allows the file provider to provide configurations to traefik // using the given configuration channel. func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error { - configuration, err := p.loadConfig() + configuration, err := p.LoadConfig() if err != nil { return err @@ -52,6 +52,15 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s return nil } +// LoadConfig loads configuration either from file or a directory specified by 'Filename'/'Directory' +// and returns a 'Configuration' object +func (p *Provider) LoadConfig() (*types.Configuration, error) { + if p.Directory != "" { + return loadFileConfigFromDirectory(p.Directory, nil) + } + return loadFileConfig(p.Filename) +} + 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() if err != nil { @@ -88,6 +97,27 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh return nil } +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 + } + + configuration, err := p.LoadConfig() + + if err != nil { + log.Errorf("Error occurred during watcher callback: %s", err) + return + } + + sendConfigToChannel(configurationChan, configuration) +} + func sendConfigToChannel(configurationChan chan<- types.ConfigMessage, configuration *types.Configuration) { configurationChan <- types.ConfigMessage{ ProviderName: "file", @@ -168,32 +198,3 @@ func loadFileConfigFromDirectory(directory string, configuration *types.Configur } return configuration, nil } - -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 - } - - configuration, err := p.loadConfig() - - if err != nil { - log.Errorf("Error occurred during watcher callback: %s", err) - return - } - - sendConfigToChannel(configurationChan, configuration) -} - -func (p *Provider) loadConfig() (*types.Configuration, error) { - if p.Directory != "" { - return loadFileConfigFromDirectory(p.Directory, nil) - } - - return loadFileConfig(p.Filename) -}