diff --git a/cmd/remco/config.go b/cmd/remco/config.go index 9803c867..2e1f1044 100644 --- a/cmd/remco/config.go +++ b/cmd/remco/config.go @@ -69,10 +69,11 @@ type DefaultBackends struct { // Resource is the representation of an resource configuration type Resource struct { - Exec template.ExecConfig - StartCmd string `toml:"start_cmd" json:"reload_cmd"` - Template []*template.Renderer - Backends BackendConfigs `toml:"backend"` + Exec template.ExecConfig + StartCmd string `toml:"start_cmd" json:"start_cmd"` + ReloadCmd string `toml:"reload_cmd" json:"reload_cmd"` + Template []*template.Renderer + Backends BackendConfigs `toml:"backend"` // defaults to the filename of the resource Name string diff --git a/cmd/remco/supervisor.go b/cmd/remco/supervisor.go index 8196a359..5589db79 100644 --- a/cmd/remco/supervisor.go +++ b/cmd/remco/supervisor.go @@ -188,6 +188,7 @@ func (ru *Supervisor) runResource(r []Resource, stop, stopped chan struct{}) { Template: r.Template, Name: r.Name, StartCmd: r.StartCmd, + ReloadCmd: r.ReloadCmd, Connectors: backendConfigs, } res, err := template.NewResourceFromResourceConfig(ctx, ru.reapLock, rsc) diff --git a/pkg/template/renderer.go b/pkg/template/renderer.go index abdfe41f..2b82a93b 100644 --- a/pkg/template/renderer.go +++ b/pkg/template/renderer.go @@ -108,7 +108,7 @@ func (s *Renderer) createStageFile(funcMap map[string]interface{}) error { // overwriting the target config file. Finally, syncFile will run a reload command // if set to have the application or service pick up the changes. // It returns a boolean indicating if the file has changed and an error if any. -func (s *Renderer) syncFiles() (bool, error) { +func (s *Renderer) syncFiles(runCommands bool) (bool, error) { var changed bool staged := s.stageFile.Name() defer os.Remove(staged) @@ -128,8 +128,10 @@ func (s *Renderer) syncFiles() (bool, error) { "config": s.Dst, }).Info("target config out of sync") - if err := s.check(staged); err != nil { - return changed, errors.Wrap(err, "config check failed") + if runCommands { + if err := s.check(staged); err != nil { + return changed, errors.Wrap(err, "config check failed") + } } s.logger.WithFields(logrus.Fields{ @@ -148,8 +150,10 @@ func (s *Renderer) syncFiles() (bool, error) { os.Chown(s.Dst, s.UID, s.GID) changed = true - if err := s.reload(); err != nil { - return changed, errors.Wrap(err, "reload command failed") + if runCommands { + if err := s.reload(); err != nil { + return changed, errors.Wrap(err, "reload command failed") + } } s.logger.WithFields(logrus.Fields{ @@ -236,7 +240,7 @@ func (s *Renderer) reload() error { } func execCommand(cmd string, logger *logrus.Entry, rl *sync.RWMutex) ([]byte, error) { - logger.Debug("Running " + cmd) + logger.Debugf("Running %q", cmd) c := exec.Command("/bin/sh", "-c", cmd) if rl != nil { diff --git a/pkg/template/resource.go b/pkg/template/resource.go index d3e83c54..93163ad6 100644 --- a/pkg/template/resource.go +++ b/pkg/template/resource.go @@ -47,8 +47,9 @@ type Resource struct { sources []*Renderer logger *logrus.Entry - exec Executor - startCmd string + exec Executor + startCmd string + reloadCmd string // SignalChan is a channel to send os.Signal's to all child processes. SignalChan chan os.Signal @@ -60,8 +61,9 @@ type Resource struct { // ResourceConfig is a configuration struct to create a new resource. type ResourceConfig struct { - Exec ExecConfig - StartCmd string + Exec ExecConfig + StartCmd string + ReloadCmd string // Template is the configuration for all template options. // You can configure as much template-destination pairs as you like. @@ -92,7 +94,7 @@ func NewResourceFromResourceConfig(ctx context.Context, reapLock *sync.RWMutex, logger := log.WithFields(logrus.Fields{"resource": r.Name}) exec := NewExecutor(r.Exec.Command, r.Exec.ReloadSignal, r.Exec.KillSignal, r.Exec.KillTimeout, r.Exec.Splay, logger) - res, err := NewResource(backendList, r.Template, r.Name, exec, r.StartCmd) + res, err := NewResource(backendList, r.Template, r.Name, exec, r.StartCmd, r.ReloadCmd) if err != nil { for _, v := range backendList { v.Close() @@ -102,7 +104,7 @@ func NewResourceFromResourceConfig(ctx context.Context, reapLock *sync.RWMutex, } // NewResource creates a Resource. -func NewResource(backends []Backend, sources []*Renderer, name string, exec Executor, startCmd string) (*Resource, error) { +func NewResource(backends []Backend, sources []*Renderer, name string, exec Executor, startCmd, reloadCmd string) (*Resource, error) { if len(backends) == 0 { return nil, fmt.Errorf("a valid StoreClient is required") } @@ -125,6 +127,7 @@ func NewResource(backends []Backend, sources []*Renderer, name string, exec Exec SignalChan: make(chan os.Signal, 1), exec: exec, startCmd: startCmd, + reloadCmd: reloadCmd, } // initialize the inidividual backend memkv Stores @@ -192,14 +195,14 @@ func (t *Resource) setVars(storeClient Backend) error { return nil } -func (t *Resource) createStageFileAndSync() (bool, error) { +func (t *Resource) createStageFileAndSync(runCommands bool) (bool, error) { var changed bool for _, s := range t.sources { err := s.createStageFile(t.funcMap) if err != nil { return changed, errors.Wrap(err, "create stage file failed") } - c, err := s.syncFiles() + c, err := s.syncFiles(runCommands) changed = changed || c if err != nil { return changed, errors.Wrap(err, "sync files failed") @@ -213,7 +216,7 @@ func (t *Resource) createStageFileAndSync() (bool, error) { // from the store, then we stage a candidate configuration file, and finally sync // things up. // It returns an error if any. -func (t *Resource) process(storeClients []Backend) (bool, error) { +func (t *Resource) process(storeClients []Backend, runCommands bool) (bool, error) { var changed bool var err error for _, storeClient := range storeClients { @@ -224,7 +227,7 @@ func (t *Resource) process(storeClients []Backend) (bool, error) { } } } - if changed, err = t.createStageFileAndSync(); err != nil { + if changed, err = t.createStageFileAndSync(runCommands); err != nil { return changed, errors.Wrap(err, "createStageFileAndSync failed") } return changed, nil @@ -255,7 +258,7 @@ retryloop: case <-ctx.Done(): return case <-retryChan: - if _, err := t.process(t.backends); err != nil { + if _, err := t.process(t.backends, t.startCmd == ""); err != nil { switch err.(type) { case berr.BackendError: err := err.(berr.BackendError) @@ -344,7 +347,7 @@ retryloop: for { select { case storeClient := <-processChan: - changed, err := t.process([]Backend{storeClient}) + changed, err := t.process([]Backend{storeClient}, true) if err != nil { switch err.(type) { case berr.BackendError: @@ -356,6 +359,13 @@ retryloop: if err := t.exec.Reload(); err != nil { t.logger.Error(err) } + + if t.reloadCmd != "" { + output, err := execCommand(t.reloadCmd, t.logger, nil) + if err != nil { + t.logger.Error(fmt.Sprintf("failed to execute the resource reload cmd - %q", string(output))) + } + } } case s := <-t.SignalChan: t.exec.SignalChild(s) diff --git a/pkg/template/resource_test.go b/pkg/template/resource_test.go index 2b7e3d1b..bdcc1613 100644 --- a/pkg/template/resource_test.go +++ b/pkg/template/resource_test.go @@ -68,7 +68,7 @@ func (s *ResourceSuite) SetUpSuite(t *C) { } exec := NewExecutor("", "", "", 0, 0, nil) - res, err := NewResource([]Backend{s.backend}, []*Renderer{s.renderer}, "test", exec, "") + res, err := NewResource([]Backend{s.backend}, []*Renderer{s.renderer}, "test", exec, "", "") t.Assert(err, IsNil) s.resource = res } @@ -103,12 +103,12 @@ func (s *ResourceSuite) TestSetVars(t *C) { } func (s *ResourceSuite) TestCreateStageFileAndSync(t *C) { - _, err := s.resource.createStageFileAndSync() + _, err := s.resource.createStageFileAndSync(true) t.Check(err, IsNil) } func (s *ResourceSuite) TestProcess(t *C) { - _, err := s.resource.process(s.resource.backends) + _, err := s.resource.process(s.resource.backends, true) t.Check(err, IsNil) data, err := ioutil.ReadFile("/tmp/remco-basic-test.conf")