Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a run-schedule command #259

Merged
merged 1 commit into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cSpell.words": [
"afero",
"caffeinate",
"creativeprojects",
"crond",
"ionice",
"journalctl",
Expand Down
2 changes: 1 addition & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ coverage:
threshold: "2%"
patch:
default:
target: "80%"
target: "70%"
threshold: "2%"
142 changes: 112 additions & 30 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

func getOwnCommands() []ownCommand {
return []ownCommand{
// commands that don't need loading the configuration
{
name: "help",
description: "display help (use resticprofile help [command])",
Expand All @@ -56,6 +57,29 @@
needConfiguration: false,
flags: map[string]string{"-v, --verbose": "display detailed version information"},
},
{
name: "random-key",
description: "generate a cryptographically secure random key to use as a restic keyfile",
action: randomKey,
needConfiguration: false,
hide: true, // replaced by the generate command
},
{
name: "generate",
description: "generate resources such as random key, bash/zsh completion scripts, etc.",
longDescription: "The \"generate\" command is used to create various resources and print them to stdout",
action: generateCommand,
needConfiguration: false,
hide: false,
flags: map[string]string{
"--random-key [size]": "generate a cryptographically secure random key to use as a restic keyfile (size defaults to 1024 when omitted)",
"--config-reference [--version 0.15] [template]": "generate a config file reference from a go template (defaults to the built-in markdown template when omitted)",
"--json-schema [--version 0.15] [v1|v2]": "generate a JSON schema that validates resticprofile configuration files in YAML or JSON format",
"--bash-completion": "generate a shell completion script for bash",
"--zsh-completion": "generate a shell completion script for zsh",
},
},
// commands that need the configuration
{
name: "profiles",
description: "display profile names from the configuration file",
Expand All @@ -70,13 +94,6 @@
action: showProfile,
needConfiguration: true,
},
{
name: "random-key",
description: "generate a cryptographically secure random key to use as a restic keyfile",
action: randomKey,
needConfiguration: false,
hide: true,
},
{
name: "schedule",
description: "schedule jobs from a profile (or of all profiles)",
Expand Down Expand Up @@ -108,19 +125,15 @@
flags: map[string]string{"--all": "display the status of all scheduled jobs of all profiles"},
},
{
name: "generate",
description: "generate resources such as random key, bash/zsh completion scripts, etc.",
longDescription: "The \"generate\" command is used to create various resources and print them to stdout",
action: generateCommand,
needConfiguration: false,
name: "run-schedule",
description: "runs a scheduled job. This command should only be called by the scheduling service",
longDescription: "The \"run-schedule\" command loads the scheduled job configuration from the name in parameter and runs the restic command with the arguments defined in the profile. The name in parameter is <command>@<profile-name> for the configuration file v1, and the schedule name for the configuration file v2+.",
pre: preRunSchedule,
action: runSchedule,
needConfiguration: true,
hide: false,
flags: map[string]string{
"--random-key [size]": "generate a cryptographically secure random key to use as a restic keyfile (size defaults to 1024 when omitted)",
"--config-reference [--version 0.15] [template]": "generate a config file reference from a go template (defaults to the built-in markdown template when omitted)",
"--json-schema [--version 0.15] [v1|v2]": "generate a JSON schema that validates resticprofile configuration files in YAML or JSON format",
"--bash-completion": "generate a shell completion script for bash",
"--zsh-completion": "generate a shell completion script for zsh",
},
hideInCompletion: true,
noProfile: true,
},
// hidden commands
{
Expand Down Expand Up @@ -328,10 +341,9 @@
return nil
}

func showSchedules(output io.Writer, schedulesConfig []*config.ScheduleConfig) {
for _, schedule := range schedulesConfig {
export := schedule.Export()
err := config.ShowStruct(output, export, "schedule "+export.Profiles[0]+"-"+export.Command)
func showSchedules(output io.Writer, schedules []*config.Schedule) {
for _, schedule := range schedules {
err := config.ShowStruct(output, schedule, "schedule "+schedule.CommandName+"@"+schedule.Profiles[0])
if err != nil {
fmt.Fprintln(output, err)
}
Expand Down Expand Up @@ -408,7 +420,7 @@
type profileJobs struct {
scheduler schedule.SchedulerConfig
profile string
jobs []*config.ScheduleConfig
jobs []*config.Schedule
}

allJobs := make([]profileJobs, 0, 1)
Expand Down Expand Up @@ -516,7 +528,7 @@
return nil
}

func statusScheduleProfile(scheduler schedule.SchedulerConfig, profile *config.Profile, schedules []*config.ScheduleConfig, flags commandLineFlags) error {
func statusScheduleProfile(scheduler schedule.SchedulerConfig, profile *config.Profile, schedules []*config.Schedule, flags commandLineFlags) error {

Check warning on line 531 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L531

Added line #L531 was not covered by tests
displayProfileDeprecationNotices(profile)

err := statusJobs(schedule.NewHandler(scheduler), flags.name, schedules)
Expand All @@ -526,7 +538,7 @@
return nil
}

func getScheduleJobs(c *config.Config, flags commandLineFlags) (schedule.SchedulerConfig, *config.Profile, []*config.ScheduleConfig, error) {
func getScheduleJobs(c *config.Config, flags commandLineFlags) (schedule.SchedulerConfig, *config.Profile, []*config.Schedule, error) {
global, err := c.GetGlobalSection()
if err != nil {
return nil, nil, nil, fmt.Errorf("cannot load global section: %w", err)
Expand All @@ -543,14 +555,14 @@
return schedule.NewSchedulerConfig(global), profile, profile.Schedules(), nil
}

func requireScheduleJobs(schedules []*config.ScheduleConfig, flags commandLineFlags) error {
func requireScheduleJobs(schedules []*config.Schedule, flags commandLineFlags) error {
if len(schedules) == 0 {
return fmt.Errorf("no schedule found for profile '%s'", flags.name)
}
return nil
}

func getRemovableScheduleJobs(c *config.Config, flags commandLineFlags) (schedule.SchedulerConfig, *config.Profile, []*config.ScheduleConfig, error) {
func getRemovableScheduleJobs(c *config.Config, flags commandLineFlags) (schedule.SchedulerConfig, *config.Profile, []*config.Schedule, error) {
scheduler, profile, schedules, err := getScheduleJobs(c, flags)
if err != nil {
return nil, nil, nil, err
Expand All @@ -560,18 +572,88 @@
for _, command := range profile.SchedulableCommands() {
declared := false
for _, s := range schedules {
if declared = s.SubTitle == command; declared {
if declared = s.CommandName == command; declared {
break
}
}
if !declared {
schedules = append(schedules, config.NewRemoveOnlyConfig(profile.Name, command))
schedules = append(schedules, config.NewEmptySchedule(profile.Name, command))
}
}

return scheduler, profile, schedules, nil
}

func preRunSchedule(ctx *Context) error {
if len(ctx.request.arguments) < 1 {
return errors.New("run-schedule command expects one argument: schedule name")
}
scheduleName := ctx.request.arguments[0]
// temporarily allow v2 configuration to run v1 schedules
// if ctx.config.GetVersion() < config.Version02
{
// schedule name is in the form "command@profile"
commandName, profileName, ok := strings.Cut(scheduleName, "@")
if !ok {
return errors.New("the expected format of the schedule name is <command>@<profile-name>")
}
ctx.request.profile = profileName
ctx.request.schedule = scheduleName
ctx.command = commandName
// remove the parameter from the arguments
ctx.request.arguments = ctx.request.arguments[1:]

// don't save the profile in the context now, it's only loaded but not prepared
profile, err := ctx.config.GetProfile(profileName)
if err != nil || profile == nil {
return fmt.Errorf("cannot load profile '%s': %w", profileName, err)
}
// get the list of all scheduled commands to find the current command
schedules := profile.Schedules()
for _, schedule := range schedules {
if schedule.CommandName == ctx.command {
ctx.schedule = schedule
prepareScheduledProfile(ctx)
break

Check warning on line 617 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L612-L617

Added lines #L612 - L617 were not covered by tests
}
}
}
return nil

Check warning on line 621 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L621

Added line #L621 was not covered by tests
}

func prepareScheduledProfile(ctx *Context) {
clog.Debugf("preparing scheduled profile %q", ctx.request.schedule)

Check warning on line 625 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L624-L625

Added lines #L624 - L625 were not covered by tests
// log file
if len(ctx.schedule.Log) > 0 {
ctx.logTarget = ctx.schedule.Log

Check warning on line 628 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L627-L628

Added lines #L627 - L628 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 634 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L631-L634

Added lines #L631 - L634 were not covered by tests
}
// lock
if ctx.schedule.GetLockWait() > 0 {
ctx.lockWait = ctx.schedule.LockWait

Check warning on line 638 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L637-L638

Added lines #L637 - L638 were not covered by tests
}
if ctx.schedule.GetLockMode() == config.ScheduleLockModeDefault {
if ctx.schedule.GetLockWait() > 0 {
ctx.lockWait = ctx.schedule.GetLockWait()

Check warning on line 642 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L640-L642

Added lines #L640 - L642 were not covered by tests
}
} else if ctx.schedule.GetLockMode() == config.ScheduleLockModeIgnore {
ctx.noLock = true

Check warning on line 645 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L644-L645

Added lines #L644 - L645 were not covered by tests
}
}

func runSchedule(_ io.Writer, cmdCtx commandContext) error {
err := startProfileOrGroup(&cmdCtx.Context)
if err != nil {
return err
}
return nil

Check warning on line 654 in commands.go

View check run for this annotation

Codecov / codecov/patch

commands.go#L654

Added line #L654 was not covered by tests
}

func testElevationCommand(_ io.Writer, ctx commandContext) error {
if ctx.flags.isChild {
client := remote.NewClient(ctx.flags.parentPort)
Expand Down
4 changes: 2 additions & 2 deletions commands_display.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ func displayOwnCommandHelp(output io.Writer, commandName string, ctx commandCont
commandFlags = "[command specific flags]"
}
out("Usage:\n")
out("\t%s %s\n\n", getCommonUsageHelpLine(command.name, command.needConfiguration), commandFlags)
out("\t%s %s\n\n", getCommonUsageHelpLine(command.name, command.needConfiguration && !command.noProfile), commandFlags)

var flags []string
var flags = make([]string, 0, len(command.flags))
for f, _ := range command.flags {
flags = append(flags, f)
}
Expand Down