Skip to content

Commit 245a440

Browse files
Systemd template (#75)
* extract templates * refactoring tests and use afero * can't use template.Must with loading external templ * todo: finish wiring up unit and timer templates to generate * fix for macos & windows * small refactoring * fix tests under windows
1 parent 1fa7d48 commit 245a440

32 files changed

+740
-161
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"cSpell.words": [
3+
"afero",
34
"crond",
45
"ionice",
56
"launchd",

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2074,6 +2074,8 @@ None of these flags are passed on the restic command line
20742074
* **restic-stale-lock-age**: duration
20752075
* **min-memory**: integer (MB)
20762076
* **scheduler**: string (`crond` is the only non-default value)
2077+
* **systemd-unit-template**: string (file containing a go template to generate systemd unit file)
2078+
* **systemd-timer-template**: string (file containing a go template to generate systemd timer file)
20772079

20782080
`[profile]`
20792081

commands.go

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"fmt"
88
"io"
99
"os"
10-
"path/filepath"
1110
"runtime"
1211
"runtime/debug"
1312
"sort"
@@ -282,14 +281,6 @@ func showProfile(output io.Writer, c *config.Config, flags commandLineFlags, arg
282281
// Display deprecation notice
283282
displayProfileDeprecationNotices(profile)
284283

285-
// All files in the configuration are relative to the configuration file, NOT the folder where resticprofile is started
286-
// So we need to fix all relative files
287-
rootPath := filepath.Dir(c.GetConfigFile())
288-
if rootPath != "." {
289-
clog.Debugf("files in configuration are relative to '%s'", rootPath)
290-
}
291-
profile.SetRootPath(rootPath)
292-
293284
config.ShowStruct(os.Stdout, profile, flags.name)
294285
fmt.Println("")
295286
return nil
@@ -357,8 +348,9 @@ func flagsForProfile(flags commandLineFlags, profileName string) commandLineFlag
357348
// createSchedule accepts one argument from the commandline: --no-start
358349
func createSchedule(_ io.Writer, c *config.Config, flags commandLineFlags, args []string) error {
359350
type profileJobs struct {
360-
scheduler, profile string
361-
jobs []*config.ScheduleConfig
351+
scheduler schedule.SchedulerType
352+
profile string
353+
jobs []*config.ScheduleConfig
362354
}
363355

364356
allJobs := make([]profileJobs, 0, 1)
@@ -456,7 +448,7 @@ func statusSchedule(w io.Writer, c *config.Config, flags commandLineFlags, args
456448
return nil
457449
}
458450

459-
func statusScheduleProfile(scheduler string, profile *config.Profile, schedules []*config.ScheduleConfig, flags commandLineFlags) error {
451+
func statusScheduleProfile(scheduler schedule.SchedulerType, profile *config.Profile, schedules []*config.ScheduleConfig, flags commandLineFlags) error {
460452
displayProfileDeprecationNotices(profile)
461453

462454
err := statusJobs(scheduler, flags.name, convertSchedules(schedules))
@@ -466,21 +458,21 @@ func statusScheduleProfile(scheduler string, profile *config.Profile, schedules
466458
return nil
467459
}
468460

469-
func getScheduleJobs(c *config.Config, flags commandLineFlags) (string, *config.Profile, []*config.ScheduleConfig, error) {
461+
func getScheduleJobs(c *config.Config, flags commandLineFlags) (schedule.SchedulerType, *config.Profile, []*config.ScheduleConfig, error) {
470462
global, err := c.GetGlobalSection()
471463
if err != nil {
472-
return "", nil, nil, fmt.Errorf("cannot load global section: %w", err)
464+
return nil, nil, nil, fmt.Errorf("cannot load global section: %w", err)
473465
}
474466

475467
profile, err := c.GetProfile(flags.name)
476468
if err != nil {
477-
return "", nil, nil, fmt.Errorf("cannot load profile '%s': %w", flags.name, err)
469+
return nil, nil, nil, fmt.Errorf("cannot load profile '%s': %w", flags.name, err)
478470
}
479471
if profile == nil {
480-
return "", nil, nil, fmt.Errorf("profile '%s' not found", flags.name)
472+
return nil, nil, nil, fmt.Errorf("profile '%s' not found", flags.name)
481473
}
482474

483-
return global.Scheduler, profile, profile.Schedules(), nil
475+
return schedule.NewSchedulerType(global), profile, profile.Schedules(), nil
484476
}
485477

486478
func requireScheduleJobs(schedules []*config.ScheduleConfig, flags commandLineFlags) error {
@@ -490,10 +482,10 @@ func requireScheduleJobs(schedules []*config.ScheduleConfig, flags commandLineFl
490482
return nil
491483
}
492484

493-
func getRemovableScheduleJobs(c *config.Config, flags commandLineFlags) (string, *config.Profile, []schedule.Config, error) {
485+
func getRemovableScheduleJobs(c *config.Config, flags commandLineFlags) (schedule.SchedulerType, *config.Profile, []schedule.Config, error) {
494486
scheduler, profile, schedules, err := getScheduleJobs(c, flags)
495487
if err != nil {
496-
return "", nil, nil, err
488+
return nil, nil, nil, err
497489
}
498490

499491
configs := convertSchedules(schedules)

commands_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ schedule = "daily"
112112
declaredCount := 0
113113

114114
for _, jobConfig := range schedules {
115-
scheduler := schedule.NewScheduler("", jobConfig.Title())
115+
scheduler := schedule.NewScheduler(schedule.SchedulerDefaultOS{}, jobConfig.Title())
116116
defer func(s schedule.Scheduler) { s.Close() }(scheduler) // Capture current ref to scheduler to be able to close it when function returns.
117117

118118
if jobConfig.SubTitle() == "check" {

config/confidential_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestConfidentialURLs(t *testing.T) {
8585
repository = "%s"
8686
`, url)
8787

88-
profile, err := getProfile("toml", testConfig, "profile")
88+
profile, err := getProfile("toml", testConfig, "profile", "")
8989
assert.Nil(t, err)
9090
assert.NotNil(t, profile)
9191

@@ -149,7 +149,7 @@ func TestConfidentialEnvironment(t *testing.T) {
149149
%s = "%s"
150150
`, name, defaultUrl)
151151

152-
profile, err := getProfile("toml", testConfig, "profile")
152+
profile, err := getProfile("toml", testConfig, "profile", "")
153153
assert.Nil(t, err)
154154
assert.NotNil(t, profile)
155155

@@ -180,7 +180,7 @@ profile:
180180
MY_TOKEN: false
181181
MY_PASSWORD: "otherval"
182182
`
183-
profile, err := getProfile("yaml", testConfig, "profile")
183+
profile, err := getProfile("yaml", testConfig, "profile", "")
184184
assert.Nil(t, err)
185185
assert.NotNil(t, profile)
186186

@@ -206,7 +206,7 @@ profile:
206206
env:
207207
MY_PASSWORD: "otherval"
208208
`
209-
profile, err := getProfile("yaml", testConfig, "profile")
209+
profile, err := getProfile("yaml", testConfig, "profile", "")
210210
assert.Nil(t, err)
211211
assert.NotNil(t, profile)
212212

@@ -220,7 +220,7 @@ func TestGetNonConfidentialArgs(t *testing.T) {
220220
profile:
221221
repository: "` + fmt.Sprintf(repo, "password") + `"
222222
`
223-
profile, err := getProfile("yaml", testConfig, "profile")
223+
profile, err := getProfile("yaml", testConfig, "profile", "")
224224
assert.NoError(t, err)
225225
assert.NotNil(t, profile)
226226

config/config.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"path/filepath"
1010
"reflect"
1111
"strings"
12+
"sync"
1213
"text/template"
1314

1415
"github.com/creativeprojects/clog"
@@ -55,6 +56,8 @@ var (
5556
confidentialValueDecoder(),
5657
sliceOfMapsToMapHookFunc(),
5758
))
59+
60+
rootPathMessage = sync.Once{}
5861
)
5962

6063
// newConfig instantiate a new Config object
@@ -127,6 +130,7 @@ func LoadFile(configFile, format string) (*Config, error) {
127130
}
128131

129132
// Load configuration from reader
133+
// This should only be used for unit tests
130134
func Load(input io.Reader, format string) (*Config, error) {
131135
c := newConfig(format)
132136
err := c.addTemplate(input, c.configFile, true)
@@ -336,6 +340,18 @@ func (c *Config) GetGlobalSection() (*Global, error) {
336340
if err != nil {
337341
return nil, err
338342
}
343+
344+
// All files in the configuration are relative to the configuration file,
345+
// NOT the folder where resticprofile is started
346+
// So we need to fix all relative files
347+
rootPath := filepath.Dir(c.GetConfigFile())
348+
if rootPath != "." {
349+
rootPathMessage.Do(func() {
350+
clog.Debugf("files in configuration are relative to %q", rootPath)
351+
})
352+
}
353+
global.SetRootPath(rootPath)
354+
339355
return global, nil
340356
}
341357

@@ -401,7 +417,21 @@ func (c *Config) GetProfile(profileKey string) (*Profile, error) {
401417
return nil, err
402418
}
403419
}
404-
return c.getProfile(profileKey)
420+
profile, err := c.getProfile(profileKey)
421+
if err != nil {
422+
return nil, err
423+
}
424+
// profile returned CAN be nil
425+
if profile == nil {
426+
return nil, nil
427+
}
428+
// All files in the configuration are relative to the configuration file,
429+
// NOT the folder where resticprofile is started
430+
// So we need to fix all relative files
431+
rootPath := filepath.Dir(c.GetConfigFile())
432+
profile.SetRootPath(rootPath)
433+
434+
return profile, nil
405435
}
406436

407437
// getProfile from configuration
@@ -410,6 +440,7 @@ func (c *Config) getProfile(profileKey string) (*Profile, error) {
410440
var profile *Profile
411441

412442
if !c.IsSet(profileKey) {
443+
// key not found => returns a nil profile
413444
return nil, nil
414445
}
415446

config/flag_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ type testStruct struct {
115115
noGlobString string `argument:"no-glob-string" argument-type:"no-glob"`
116116
emptyInt int `argument:"empty-int"`
117117
someInt int `argument:"some-int"`
118+
uInt uint `argument:"u-int"`
118119
notIncluded bool
119120
}
120121

@@ -124,6 +125,7 @@ func TestConvertStructToArgs(t *testing.T) {
124125
someBoolFalse: false,
125126
emptyInt: 0,
126127
someInt: 10,
128+
uInt: 20,
127129
emptyString: "",
128130
globString: "test",
129131
noGlobString: "test",
@@ -134,6 +136,7 @@ func TestConvertStructToArgs(t *testing.T) {
134136
assert.Equal(t, map[string][]string{
135137
"some-bool-true": {},
136138
"some-int": {"10"},
139+
"u-int": {"20"},
137140
"glob-string": {"test"},
138141
"no-glob-string": {"test"},
139142
}, args.ToMap())
@@ -145,6 +148,7 @@ func TestConvertPointerStructToArgs(t *testing.T) {
145148
someBoolFalse: false,
146149
emptyInt: 0,
147150
someInt: 10,
151+
uInt: 20,
148152
emptyString: "",
149153
globString: "test",
150154
noGlobString: "test",
@@ -155,6 +159,7 @@ func TestConvertPointerStructToArgs(t *testing.T) {
155159
assert.Equal(t, map[string][]string{
156160
"some-bool-true": {},
157161
"some-int": {"10"},
162+
"u-int": {"20"},
158163
"glob-string": {"test"},
159164
"no-glob-string": {"test"},
160165
}, args.ToMap())

config/global.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ type Global struct {
2121
MinMemory uint64 `mapstructure:"min-memory"`
2222
Scheduler string `mapstructure:"scheduler"`
2323
LegacyArguments bool `mapstructure:"legacy-arguments"` // broken arguments mode (before v0.15.0)
24+
SystemdUnitTemplate string `mapstructure:"systemd-unit-template"`
25+
SystemdTimerTemplate string `mapstructure:"systemd-timer-template"`
2426
}
2527

2628
// NewGlobal instantiates a new Global with default values
@@ -34,3 +36,8 @@ func NewGlobal() *Global {
3436
MinMemory: constants.DefaultMinMemory,
3537
}
3638
}
39+
40+
func (p *Global) SetRootPath(rootPath string) {
41+
p.SystemdUnitTemplate = fixPath(p.SystemdUnitTemplate, expandEnv, absolutePrefix(rootPath))
42+
p.SystemdTimerTemplate = fixPath(p.SystemdTimerTemplate, expandEnv, absolutePrefix(rootPath))
43+
}

config/helpers_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66

77
// Helpers for tests
88

9-
func getProfile(configFormat, configString, profileKey string) (*Profile, error) {
9+
func getProfile(configFormat, configString, profileKey, rootPath string) (*Profile, error) {
1010
c, err := Load(bytes.NewBufferString(configString), configFormat)
1111
if err != nil {
1212
return nil, err
@@ -16,6 +16,9 @@ func getProfile(configFormat, configString, profileKey string) (*Profile, error)
1616
if err != nil {
1717
return nil, err
1818
}
19+
if rootPath != "" {
20+
profile.SetRootPath(rootPath)
21+
}
1922
return profile, nil
2023
}
2124

0 commit comments

Comments
 (0)