Skip to content

Commit 01e83e8

Browse files
authored
Add runtime reload for global settings (#214)
## Summary - compare global options when reloading config - rebuild scheduler and job middlewares on global change - add `ResetMiddlewares` to core - test global reload of log-level and save settings - document which settings are hot-reloaded ## Testing - `go vet ./...` - `go test ./...` ------ https://chatgpt.com/codex/tasks/task_b_686961668de88333ad17ed6741cfb032
2 parents 04cea79 + f15bdc6 commit 01e83e8

5 files changed

Lines changed: 100 additions & 0 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ Ofelia merges options from multiple sources in the following order. Values from
127127

128128
The daemon watches `config.ini` and reloads it automatically when the file changes.
129129

130+
Job definitions and most `[global]` middleware options (`slack-*`, `save-*`,
131+
`mail-*`, `log-level`, `max-runtime`) are applied on reload. Options that start
132+
servers (`enable-web`, `web-address`, `enable-pprof`, `pprof-address`) and all
133+
`[docker]` settings require restarting the daemon.
134+
130135
## Configuration
131136

132137
### Jobs

cli/config.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"os"
66
"path/filepath"
7+
"reflect"
78
"sort"
89
"strings"
910
"time"
@@ -257,6 +258,7 @@ type jobConfig interface {
257258
Hash() (string, error)
258259
GetJobSource() JobSource
259260
SetJobSource(JobSource)
261+
ResetMiddlewares(...core.Middleware)
260262
}
261263

262264
// syncJobMap updates the scheduler and the provided job map based on the parsed
@@ -400,9 +402,30 @@ func (c *Config) iniConfigUpdate() error {
400402
if err != nil {
401403
return err
402404
}
405+
globalChanged := !reflect.DeepEqual(parsed.Global, c.Global)
403406
c.configFiles = files
404407
c.configModTime = latest
405408
c.logger.Debugf("applied config files from %s", strings.Join(files, ", "))
409+
if globalChanged {
410+
c.Global = parsed.Global
411+
c.sh.ResetMiddlewares()
412+
c.buildSchedulerMiddlewares(c.sh)
413+
for _, j := range c.sh.Jobs {
414+
if jc, ok := j.(jobConfig); ok {
415+
jc.ResetMiddlewares()
416+
jc.buildMiddlewares()
417+
j.Use(c.sh.Middlewares()...)
418+
}
419+
}
420+
for _, j := range c.sh.Disabled {
421+
if jc, ok := j.(jobConfig); ok {
422+
jc.ResetMiddlewares()
423+
jc.buildMiddlewares()
424+
j.Use(c.sh.Middlewares()...)
425+
}
426+
}
427+
ApplyLogLevel(c.Global.LogLevel)
428+
}
406429

407430
execPrep := func(name string, j *ExecJobConfig) {
408431
defaults.Set(j)

cli/config_extra_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ package cli
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"io/ioutil"
78
"os"
89
"path/filepath"
910
"time"
1011

1112
defaults "github.com/creasty/defaults"
1213
"github.com/netresearch/ofelia/core"
14+
"github.com/netresearch/ofelia/middlewares"
15+
16+
"github.com/sirupsen/logrus"
1317

1418
. "gopkg.in/check.v1"
1519
)
@@ -345,6 +349,55 @@ func (s *SuiteConfig) TestIniConfigUpdateGlob(c *C) {
345349
c.Assert(cfg.RunJobs["foo"].GetSchedule(), Equals, "@every 10s")
346350
}
347351

352+
// TestIniConfigUpdateGlobalChange verifies global middleware options and log
353+
// level are reloaded.
354+
func (s *SuiteConfig) TestIniConfigUpdateGlobalChange(c *C) {
355+
tmp, err := ioutil.TempFile("", "ofelia_*.ini")
356+
c.Assert(err, IsNil)
357+
defer os.Remove(tmp.Name())
358+
359+
dir := c.MkDir()
360+
content1 := fmt.Sprintf("[global]\nlog-level = INFO\nsave-folder = %s\n",
361+
dir)
362+
content1 += "save-only-on-error = false\n"
363+
content1 += "[job-run \"foo\"]\nschedule = @every 5s\nimage = busybox\ncommand = echo foo\n"
364+
_, err = tmp.WriteString(content1)
365+
c.Assert(err, IsNil)
366+
tmp.Close()
367+
368+
logrus.SetLevel(logrus.InfoLevel)
369+
370+
cfg, err := BuildFromFile(tmp.Name(), &TestLogger{})
371+
c.Assert(err, IsNil)
372+
cfg.logger = &TestLogger{}
373+
cfg.dockerHandler = &DockerHandler{}
374+
cfg.sh = core.NewScheduler(&TestLogger{})
375+
cfg.buildSchedulerMiddlewares(cfg.sh)
376+
377+
ApplyLogLevel(cfg.Global.LogLevel)
378+
ms := cfg.sh.Middlewares()
379+
c.Assert(ms, HasLen, 1)
380+
saveMw := ms[0].(*middlewares.Save)
381+
c.Assert(saveMw.SaveOnlyOnError, Equals, false)
382+
c.Assert(logrus.GetLevel(), Equals, logrus.InfoLevel)
383+
384+
oldTime := cfg.configModTime
385+
content2 := fmt.Sprintf("[global]\nlog-level = DEBUG\nsave-folder = %s\nsave-only-on-error = true\n", dir)
386+
content2 += "[job-run \"foo\"]\nschedule = @every 5s\nimage = busybox\ncommand = echo foo\n"
387+
err = os.WriteFile(tmp.Name(), []byte(content2), 0o644)
388+
c.Assert(err, IsNil)
389+
c.Assert(waitForModTimeChange(tmp.Name(), oldTime), IsNil)
390+
391+
err = cfg.iniConfigUpdate()
392+
c.Assert(err, IsNil)
393+
c.Assert(cfg.Global.LogLevel, Equals, "DEBUG")
394+
ms = cfg.sh.Middlewares()
395+
c.Assert(ms, HasLen, 1)
396+
saveMw = ms[0].(*middlewares.Save)
397+
c.Assert(saveMw.SaveOnlyOnError, Equals, true)
398+
c.Assert(logrus.GetLevel(), Equals, logrus.DebugLevel)
399+
}
400+
348401
func waitForModTimeChange(path string, after time.Time) error {
349402
info, err := os.Stat(path)
350403
if err != nil {

core/common.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ func (c *middlewareContainer) Use(ms ...Middleware) {
221221
}
222222
}
223223

224+
func (c *middlewareContainer) ResetMiddlewares(ms ...Middleware) {
225+
c.m = nil
226+
c.order = nil
227+
c.Use(ms...)
228+
}
229+
224230
func (c *middlewareContainer) Middlewares() []Middleware {
225231
var ms []Middleware
226232
for _, t := range c.order {

core/common_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,19 @@ func (s *SuiteCommon) TestMiddlewareContainerUseOder(c *C) {
273273
c.Assert(ms[1], Equals, mA)
274274
}
275275

276+
func (s *SuiteCommon) TestMiddlewareContainerReset(c *C) {
277+
mA := &TestMiddleware{}
278+
mB := &TestMiddlewareAltA{}
279+
280+
container := &middlewareContainer{}
281+
container.Use(mA)
282+
container.ResetMiddlewares(mB)
283+
284+
ms := container.Middlewares()
285+
c.Assert(ms, HasLen, 1)
286+
c.Assert(ms[0], Equals, mB)
287+
}
288+
276289
type TestMiddleware struct {
277290
Called int
278291
Nested bool

0 commit comments

Comments
 (0)