Skip to content

Commit

Permalink
wire up ignore-on-battery and locks
Browse files Browse the repository at this point in the history
from schedule configuration to context
  • Loading branch information
creativeprojects committed Nov 12, 2023
1 parent 329696e commit c7775a4
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 92 deletions.
30 changes: 26 additions & 4 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,18 +608,40 @@ func preRunSchedule(ctx *Context) error {
schedules := profile.Schedules()
for _, schedule := range schedules {
if schedule.CommandName == ctx.command {
clog.Debugf("preparing scheduled profile %q", ctx.request.schedule)
ctx.schedule = schedule
if len(schedule.Log) > 0 {
ctx.logTarget = schedule.Log
}
prepareScheduledProfile(ctx)
break

Check warning on line 613 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L608-L613

Added lines #L608 - L613 were not covered by tests
}
}
}
return nil

Check warning on line 617 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L617

Added line #L617 was not covered by tests
}

func prepareScheduledProfile(ctx *Context) {
clog.Debugf("preparing scheduled profile %q", ctx.request.schedule)
// log file
if len(ctx.schedule.Log) > 0 {
ctx.logTarget = ctx.schedule.Log
}

Check warning on line 625 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L620-L625

Added lines #L620 - L625 were not covered by tests
// battery
if ctx.schedule.IgnoreOnBatteryLessThan > 0 {
ctx.stopOnBattery = ctx.schedule.IgnoreOnBatteryLessThan
} else if ctx.schedule.IgnoreOnBattery {
ctx.stopOnBattery = 100
}

Check warning on line 631 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L627-L631

Added lines #L627 - L631 were not covered by tests
// lock
if ctx.schedule.GetLockWait() > 0 {
ctx.lockWait = ctx.schedule.LockWait
}
if ctx.schedule.GetLockMode() == config.ScheduleLockModeDefault {
if ctx.schedule.GetLockWait() > 0 {
ctx.lockWait = ctx.schedule.GetLockWait()
}
} else if ctx.schedule.GetLockMode() == config.ScheduleLockModeIgnore {
ctx.noLock = true
}

Check warning on line 642 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L633-L642

Added lines #L633 - L642 were not covered by tests
}

func runSchedule(_ io.Writer, cmdCtx commandContext) error {
err := startProfileOrGroup(&cmdCtx.Context)
if err != nil {
Expand Down
11 changes: 6 additions & 5 deletions config/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,9 @@ func (o *OtherFlagsSection) SetOtherFlag(name string, value any) {
// NewProfile instantiates a new blank profile
func NewProfile(c *Config, name string) (p *Profile) {
p = &Profile{
Name: name,
config: c,
OtherSections: make(map[string]*GenericSection),
Name: name,
config: c,
OtherSections: make(map[string]*GenericSection),
PrometheusPushFormat: constants.DefaultPrometheusPushFormat,
}

Expand Down Expand Up @@ -849,16 +849,17 @@ func (p *Profile) Schedules() []*Schedule {
Profiles: []string{},
Schedules: s.Schedule,
Permission: s.SchedulePermission,
Environment: env.Values(),
Log: s.ScheduleLog,
Priority: s.SchedulePriority,
LockMode: s.ScheduleLockMode,
LockWait: s.ScheduleLockWait,
Priority: s.SchedulePriority,
Environment: env.Values(),
IgnoreOnBattery: s.ScheduleIgnoreOnBattery,
IgnoreOnBatteryLessThan: s.ScheduleIgnoreOnBatteryLessThan,
AfterNetworkOnline: s.ScheduleAfterNetworkOnline,
SystemdDropInFiles: p.SystemdDropInFiles,
ConfigFile: p.config.configFile,
Flags: map[string]string{},
}

if len(config.Log) > 0 {
Expand Down
45 changes: 44 additions & 1 deletion config/schedule.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
package config

import "time"
import (
"time"

"github.com/creativeprojects/resticprofile/constants"
)

type ScheduleLockMode int8

const (
// ScheduleLockModeDefault waits on acquiring a lock (local and repository) for up to ScheduleConfig lockWait (duration), before failing a schedule.
// With lockWait set to 0, ScheduleLockModeDefault and ScheduleLockModeFail behave the same.
ScheduleLockModeDefault = ScheduleLockMode(0)
// ScheduleLockModeFail fails immediately on a lock failure without waiting.
ScheduleLockModeFail = ScheduleLockMode(1)
// ScheduleLockModeIgnore does not create or fail on resticprofile locks. Repository locks cause an immediate failure.
ScheduleLockModeIgnore = ScheduleLockMode(2)
)

// Schedule is an intermediary object between the configuration (v1, v2+) and the ScheduleConfig object used by the scheduler.
// The object is also used to display the scheduling configuration
Expand Down Expand Up @@ -31,6 +47,33 @@ func NewEmptySchedule(profileName, command string) *Schedule {
}

Check warning on line 47 in config/schedule.go

View check run for this annotation

Codecov / codecov/patch

config/schedule.go#L43-L47

Added lines #L43 - L47 were not covered by tests
}

func (s *Schedule) GetLockMode() ScheduleLockMode {
switch s.LockMode {
case constants.ScheduleLockModeOptionFail:
return ScheduleLockModeFail
case constants.ScheduleLockModeOptionIgnore:
return ScheduleLockModeIgnore
default:
return ScheduleLockModeDefault
}
}

func (s *Schedule) GetLockWait() time.Duration {
if s.LockWait <= 2*time.Second {
return 0
}
return s.LockWait
}

func (s *Schedule) GetFlag(name string) (string, bool) {
if len(s.Flags) == 0 {
return "", false
}
// we can't do a direct return, technically the map returns only one value
value, found := s.Flags[name]
return value, found
}

func (s *Schedule) SetFlag(name, value string) {
if s.Flags == nil {
s.Flags = make(map[string]string)
Expand Down
70 changes: 70 additions & 0 deletions config/schedule_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package config

import (
"testing"
"time"

"github.com/creativeprojects/resticprofile/constants"
"github.com/stretchr/testify/assert"
)

func TestScheduleProperties(t *testing.T) {
schedule := Schedule{
ProfileName: "profile",
CommandName: "command name",
Schedules: []string{"1", "2", "3"},
Permission: "admin",
Environment: []string{"test=dev"},
Priority: "",
LockMode: "undefined",
LockWait: 1 * time.Minute,
ConfigFile: "config",
Flags: map[string]string{},
IgnoreOnBattery: false,
IgnoreOnBatteryLessThan: 0,
}

assert.Equal(t, "config", schedule.ConfigFile)
assert.Equal(t, "profile", schedule.ProfileName)
assert.Equal(t, "command name", schedule.CommandName)
assert.ElementsMatch(t, []string{"1", "2", "3"}, schedule.Schedules)
assert.Equal(t, "admin", schedule.Permission)
assert.Equal(t, []string{"test=dev"}, schedule.Environment)
assert.Equal(t, ScheduleLockModeDefault, schedule.GetLockMode())
assert.Equal(t, 60*time.Second, schedule.GetLockWait())
}

func TestLockModes(t *testing.T) {
tests := map[ScheduleLockMode]Schedule{
ScheduleLockModeDefault: {LockMode: ""},
ScheduleLockModeFail: {LockMode: constants.ScheduleLockModeOptionFail},
ScheduleLockModeIgnore: {LockMode: constants.ScheduleLockModeOptionIgnore},
}
for mode, config := range tests {
assert.Equal(t, mode, config.GetLockMode())
}
}

func TestLockWait(t *testing.T) {
tests := map[time.Duration]Schedule{
0: {LockWait: 2 * time.Second}, // min lock wait is is >2 seconds
3 * time.Second: {LockWait: 3 * time.Second},
120 * time.Hour: {LockWait: 120 * time.Hour},
}
for mode, config := range tests {
assert.Equal(t, mode, config.GetLockWait())
}
}

func TestScheduleFlags(t *testing.T) {
schedule := &Schedule{}

flag, found := schedule.GetFlag("unit")
assert.Empty(t, flag)
assert.False(t, found)

schedule.SetFlag("unit", "test")
flag, found = schedule.GetFlag("unit")
assert.Equal(t, "test", flag)
assert.True(t, found)
}
24 changes: 14 additions & 10 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"os"
"time"

"github.com/creativeprojects/resticprofile/config"
)
Expand All @@ -18,16 +19,19 @@ type Request struct {
// Not everything is always available,
// but any information should be added to the context as soon as known.
type Context struct {
request Request
flags commandLineFlags
global *config.Global
config *config.Config
binary string // where to find the restic binary
command string // which restic command to use
profile *config.Profile
schedule *config.Schedule // when profile is running with run-schedule command
sigChan chan os.Signal // termination request
logTarget string // where to send the log output
request Request
flags commandLineFlags
global *config.Global
config *config.Config
binary string // where to find the restic binary
command string // which restic command to use
profile *config.Profile
schedule *config.Schedule // when profile is running with run-schedule command
sigChan chan os.Signal // termination request
logTarget string // where to send the log output
stopOnBattery int // stop if running on battery
noLock bool // skip profile lock file
lockWait time.Duration // wait up to duration to acquire a lock
}

// WithConfig sets the configuration and global values. A new copy of the context is returned.
Expand Down
27 changes: 18 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func main() {
}

// check if we're running on battery
if shouldStopOnBattery(flags.ignoreOnBattery) {
if shouldStopOnBattery(ctx.stopOnBattery) {
exitCode = 3
return
}
Expand Down Expand Up @@ -332,6 +332,17 @@ func loadContext(flags commandLineFlags, silent bool) (*Context, error) {
if flags.log != "" {
ctx.logTarget = flags.log
}
// same for battery configuration
if flags.ignoreOnBattery > 0 {
ctx.stopOnBattery = flags.ignoreOnBattery
}
// also lock configuration
if flags.noLock {
ctx.noLock = true
}
if flags.lockWait > 0 {
ctx.lockWait = flags.lockWait
}
return ctx, nil
}

Expand Down Expand Up @@ -546,7 +557,7 @@ func runProfile(ctx *Context) error {

if ctx.request.schedule != "" {
// this is a scheduled profile
prepareScheduledProfile(ctx, profile)
loadScheduledProfile(ctx)
}

// Catch CTR-C keypress, or other signal sent by a service manager (systemd)
Expand All @@ -558,10 +569,10 @@ func runProfile(ctx *Context) error {
ctx.sigChan = sigChan
wrapper := newResticWrapper(ctx)

if ctx.flags.noLock {
if ctx.noLock {
wrapper.ignoreLock()
} else if ctx.flags.lockWait > 0 {
wrapper.maxWaitOnLock(ctx.flags.lockWait)
} else if ctx.lockWait > 0 {
wrapper.maxWaitOnLock(ctx.lockWait)
}

// add progress receivers if necessary
Expand All @@ -579,13 +590,11 @@ func runProfile(ctx *Context) error {
return nil
}

func prepareScheduledProfile(ctx *Context, profile *config.Profile) error {
func loadScheduledProfile(ctx *Context) error {
// get the list of all scheduled commands to find the current command
schedules := profile.Schedules()
schedules := ctx.profile.Schedules()
for _, schedule := range schedules {
if schedule.CommandName == ctx.command {
// TODO: do we still need to do anything here?
// clog.Debugf("preparing scheduled profile %q", ctx.request.schedule)
ctx.schedule = schedule
break
}
Expand Down
33 changes: 0 additions & 33 deletions schedule/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,10 @@ package schedule

import (
"strings"
"time"

"github.com/creativeprojects/resticprofile/constants"
)

type ScheduleLockMode int8

const (
// ScheduleLockModeDefault waits on acquiring a lock (local and repository) for up to ScheduleConfig lockWait (duration), before failing a schedule.
// With lockWait set to 0, ScheduleLockModeDefault and ScheduleLockModeFail behave the same.
ScheduleLockModeDefault = ScheduleLockMode(0)
// ScheduleLockModeFail fails immediately on a lock failure without waiting.
ScheduleLockModeFail = ScheduleLockMode(1)
// ScheduleLockModeIgnore does not create or fail on resticprofile locks. Repository locks cause an immediate failure.
ScheduleLockModeIgnore = ScheduleLockMode(2)
)

// Config contains all information to schedule a profile command
type Config struct {
ProfileName string
Expand All @@ -32,8 +19,6 @@ type Config struct {
JobDescription string
TimerDescription string
Priority string // Priority is either "background" or "standard"
LockMode string
LockWait time.Duration
ConfigFile string
Flags map[string]string // flags added to the command line
IgnoreOnBattery bool
Expand Down Expand Up @@ -68,24 +53,6 @@ func (s *Config) GetPriority() string {
return s.Priority
}

func (s *Config) GetLockMode() ScheduleLockMode {
switch s.LockMode {
case constants.ScheduleLockModeOptionFail:
return ScheduleLockModeFail
case constants.ScheduleLockModeOptionIgnore:
return ScheduleLockModeIgnore
default:
return ScheduleLockModeDefault
}
}

func (s *Config) GetLockWait() time.Duration {
if s.LockWait <= 2*time.Second {
return 0
}
return s.LockWait
}

func (s *Config) GetFlag(name string) (string, bool) {
if len(s.Flags) == 0 {
return "", false
Expand Down
Loading

0 comments on commit c7775a4

Please sign in to comment.