Skip to content

Added prune as supported, schedulable command #24

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

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,14 @@ Flags passed to the restic command line
* **read-data-subset**: string
* **with-cache**: true / false

`[profile.prune]`

Flags used by resticprofile only

* **schedule**: string OR list of strings
* **schedule-permission**: string (`user` or `system`)
* **schedule-log**: string

`[profile.mount]`

Flags passed to the restic command line
Expand Down
133 changes: 72 additions & 61 deletions config/profile.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package config

import (
"reflect"

"github.com/creativeprojects/clog"
"github.com/creativeprojects/resticprofile/constants"
)
Expand Down Expand Up @@ -28,6 +30,7 @@ type Profile struct {
Backup *BackupSection `mapstructure:"backup"`
Retention *RetentionSection `mapstructure:"retention"`
Check *OtherSectionWithSchedule `mapstructure:"check"`
Prune *OtherSectionWithSchedule `mapstructure:"prune"`
Snapshots map[string]interface{} `mapstructure:"snapshots"`
Forget map[string]interface{} `mapstructure:"forget"`
Mount map[string]interface{} `mapstructure:"mount"`
Expand All @@ -36,40 +39,40 @@ type Profile struct {

// BackupSection contains the specific configuration to the 'backup' command
type BackupSection struct {
CheckBefore bool `mapstructure:"check-before"`
CheckAfter bool `mapstructure:"check-after"`
RunBefore []string `mapstructure:"run-before"`
RunAfter []string `mapstructure:"run-after"`
UseStdin bool `mapstructure:"stdin" argument:"stdin"`
Source []string `mapstructure:"source"`
Exclude []string `mapstructure:"exclude" argument:"exclude"`
Iexclude []string `mapstructure:"iexclude" argument:"iexclude"`
ExcludeFile []string `mapstructure:"exclude-file" argument:"exclude-file"`
FilesFrom []string `mapstructure:"files-from" argument:"files-from"`
Schedule []string `mapstructure:"schedule"`
SchedulePermission string `mapstructure:"schedule-permission"`
ScheduleLog string `mapstructure:"schedule-log"`
OtherFlags map[string]interface{} `mapstructure:",remain"`
ScheduleSection `mapstructure:",squash"`
CheckBefore bool `mapstructure:"check-before"`
CheckAfter bool `mapstructure:"check-after"`
RunBefore []string `mapstructure:"run-before"`
RunAfter []string `mapstructure:"run-after"`
UseStdin bool `mapstructure:"stdin" argument:"stdin"`
Source []string `mapstructure:"source"`
Exclude []string `mapstructure:"exclude" argument:"exclude"`
Iexclude []string `mapstructure:"iexclude" argument:"iexclude"`
ExcludeFile []string `mapstructure:"exclude-file" argument:"exclude-file"`
FilesFrom []string `mapstructure:"files-from" argument:"files-from"`
OtherFlags map[string]interface{} `mapstructure:",remain"`
}

// RetentionSection contains the specific configuration to
// the 'forget' command when running as part of a backup
type RetentionSection struct {
BeforeBackup bool `mapstructure:"before-backup"`
AfterBackup bool `mapstructure:"after-backup"`
Schedule []string `mapstructure:"schedule"`
SchedulePermission string `mapstructure:"schedule-permission"`
ScheduleLog string `mapstructure:"schedule-log"`
OtherFlags map[string]interface{} `mapstructure:",remain"`
ScheduleSection `mapstructure:",squash"`
BeforeBackup bool `mapstructure:"before-backup"`
AfterBackup bool `mapstructure:"after-backup"`
OtherFlags map[string]interface{} `mapstructure:",remain"`
}

// OtherSectionWithSchedule is a section containing schedule only specific parameters
// (the other parameters being for restic)
type OtherSectionWithSchedule struct {
Schedule []string `mapstructure:"schedule"`
SchedulePermission string `mapstructure:"schedule-permission"`
ScheduleLog string `mapstructure:"schedule-log"`
OtherFlags map[string]interface{} `mapstructure:",remain"`
ScheduleSection `mapstructure:",squash"`
OtherFlags map[string]interface{} `mapstructure:",remain"`
}

type ScheduleSection struct {
Schedule []string `mapstructure:"schedule"`
SchedulePermission string `mapstructure:"schedule-permission"`
ScheduleLog string `mapstructure:"schedule-log"`
}

// NewProfile instantiates a new blank profile
Expand Down Expand Up @@ -168,6 +171,11 @@ func (p *Profile) GetCommandFlags(command string) map[string][]string {
flags = addOtherFlags(flags, p.Check.OtherFlags)
}

case constants.CommandPrune:
if p.Prune != nil && p.Prune.OtherFlags != nil {
flags = addOtherFlags(flags, p.Prune.OtherFlags)
}

case constants.CommandForget:
if p.Forget != nil {
flags = addOtherFlags(flags, p.Forget)
Expand Down Expand Up @@ -208,49 +216,52 @@ func (p *Profile) GetBackupSource() []string {

// Schedules returns a slice of ScheduleConfig that satisfy the schedule.Config interface
func (p *Profile) Schedules() []*ScheduleConfig {
// Default to 3: backup, retention and check
configs := make([]*ScheduleConfig, 0, 3)
// Backup
if p.Backup != nil && p.Backup.Schedule != nil && len(p.Backup.Schedule) > 0 {
config := &ScheduleConfig{
profileName: p.Name,
commandName: constants.CommandBackup,
schedules: p.Backup.Schedule,
permission: p.Backup.SchedulePermission,
environment: p.Environment,
nice: 10, // hard-coded for now
logfile: p.Backup.ScheduleLog,
// All SectionWithSchedule (backup, check, prune, etc)
sections := p.allSchedulableSections()
configs := make([]*ScheduleConfig, 0, len(sections))

for name, section := range sections {
if s := getScheduleSection(section); s != nil && s.Schedule != nil && len(s.Schedule) > 0 {
config := &ScheduleConfig{
profileName: p.Name,
commandName: name,
schedules: s.Schedule,
permission: s.SchedulePermission,
environment: p.Environment,
nice: 10, // hard-coded for now
logfile: s.ScheduleLog,
}

configs = append(configs, config)
}
configs = append(configs, config)
}
// Retention (forget)
if p.Retention != nil && p.Retention.Schedule != nil && len(p.Retention.Schedule) > 0 {
config := &ScheduleConfig{
profileName: p.Name,
commandName: constants.SectionConfigurationRetention,
schedules: p.Retention.Schedule,
permission: p.Retention.SchedulePermission,
environment: p.Environment,
nice: 10, // hard-coded for now
logfile: p.Retention.ScheduleLog,
}
configs = append(configs, config)

return configs
}

func (p *Profile) allSchedulableSections() map[string]interface{} {
return map[string]interface{}{
constants.CommandBackup: p.Backup,
constants.SectionConfigurationRetention: p.Retention,
constants.CommandCheck: p.Check,
constants.CommandPrune: p.Prune,
}
// Check
if p.Check != nil && p.Check.Schedule != nil && len(p.Check.Schedule) > 0 {
config := &ScheduleConfig{
profileName: p.Name,
commandName: constants.CommandCheck,
schedules: p.Check.Schedule,
permission: p.Check.SchedulePermission,
environment: p.Environment,
nice: 10, // hard-coded for now
logfile: p.Check.ScheduleLog,
}

func getScheduleSection(section interface{}) *ScheduleSection {
if !reflect.ValueOf(section).IsNil() {
switch v := section.(type) {
case *BackupSection:
return &v.ScheduleSection
case *RetentionSection:
return &v.ScheduleSection
case *OtherSectionWithSchedule:
return &v.ScheduleSection
}
configs = append(configs, config)
}
return configs
return nil
}

func addOtherFlags(flags map[string][]string, otherFlags map[string]interface{}) map[string][]string {
if len(otherFlags) == 0 {
return flags
Expand Down
47 changes: 47 additions & 0 deletions config/profile_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"fmt"
"path/filepath"
"strconv"
"testing"
Expand Down Expand Up @@ -336,3 +337,49 @@ profile:
})
}
}

func TestSchedules(t *testing.T) {
assert := assert.New(t)

testConfig := func(command string, scheduled bool) string {
schedule := ""
if scheduled {
schedule = `schedule = "@hourly"`
}

config := `
[profile]
initialize = true

[profile.%s]
%s
`
return fmt.Sprintf(config, command, schedule)
}

sections := NewProfile(nil, "").allSchedulableSections()
assert.Len(sections, 4)

for command, _ := range sections {
// Check that schedule is supported
profile, err := getProfile("toml", testConfig(command, true), "profile")
if err != nil {
t.Fatal(err)
}
assert.NotNil(profile)

config := profile.Schedules()
assert.Len(config, 1)
assert.Equal(config[0].commandName, command)
assert.Len(config[0].schedules, 1)
assert.Equal(config[0].schedules[0], "@hourly")

// Check that schedule is optional
profile, err = getProfile("toml", testConfig(command, false), "profile")
if err != nil {
t.Fatal(err)
}
assert.NotNil(profile)
assert.Empty(profile.Schedules())
}
}
12 changes: 9 additions & 3 deletions config/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,15 @@ func showSubStruct(outputWriter io.Writer, orig interface{}, prefix string) erro
continue
}
if valueOf.Field(i).Kind() == reflect.Struct {
// start of a new struct
fmt.Fprintf(buffer, "%s%s:\n", prefix, key)
err := showSubStruct(buffer, valueOf.Field(i).Interface(), prefix)
var err error
if key == ",squash" {
p := prefix[0 : len(prefix)-len(templateIndent)]
err = showSubStruct(tabWriter, valueOf.Field(i).Interface(), p)
} else {
// start of a new struct
fmt.Fprintf(buffer, "%s%s:\n", prefix, key)
err = showSubStruct(buffer, valueOf.Field(i).Interface(), prefix)
}
if err != nil {
return err
}
Expand Down
13 changes: 13 additions & 0 deletions config/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ type testPointer struct {
IsValid bool `mapstructure:"valid"`
}

type testEmbedded struct {
EmbeddedStruct `mapstructure:",squash"`
InlineValue int `mapstructure:"inline"`
}

type EmbeddedStruct struct {
Value bool `mapstructure:"value"`
}

func TestShowStruct(t *testing.T) {
testData := []showStructData{
{
Expand Down Expand Up @@ -58,6 +67,10 @@ func TestShowStruct(t *testing.T) {
input: testObject{Id: 11, Name: "test", Map: map[string][]string{"left": {"over"}}},
output: " person:\n\n id: 11\n name: test\n left: over\n\n",
},
{
input: testEmbedded{EmbeddedStruct{Value: true}, 1},
output: " value: true\n\n inline: 1\n\n",
},
}

for _, testItem := range testData {
Expand Down
3 changes: 2 additions & 1 deletion schedule/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import (
)

var (
// ScheduledSections are the command that can be scheduled (backup, retention, check)
// ScheduledSections are the command that can be scheduled (backup, retention, check, prune)
ScheduledSections = []string{
constants.CommandBackup,
constants.SectionConfigurationRetention,
constants.CommandCheck,
constants.CommandPrune,
}
)

Expand Down