Skip to content

Commit

Permalink
Output: Support extended parsing and redirection
Browse files Browse the repository at this point in the history
Also adds a status monitor when run in console mode
  • Loading branch information
jkellerer committed May 1, 2023
1 parent 0283615 commit 117f6f8
Show file tree
Hide file tree
Showing 29 changed files with 1,340 additions and 264 deletions.
5 changes: 4 additions & 1 deletion config/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,10 @@ func stringify(value reflect.Value, onlySimplyValues bool) ([]string, bool) {
sort.Strings(flatMap)
return flatMap, len(flatMap) > 0

case reflect.Interface:
case reflect.Interface, reflect.Pointer:
if value.IsNil() {
return emptyStringArray, false
}
return stringify(value.Elem(), onlySimplyValues)

default:
Expand Down
2 changes: 1 addition & 1 deletion config/flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestPointerValueShouldReturnErrorMessage(t *testing.T) {
concrete := "test"
value := &concrete
argValue, _ := stringifyValueOf(value)
assert.Equal(t, []string{"ERROR: unexpected type ptr"}, argValue)
assert.Equal(t, []string{"test"}, argValue)
}

func TestNilValueFlag(t *testing.T) {
Expand Down
3 changes: 1 addition & 2 deletions config/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,11 +659,10 @@ func NewProfileInfoForRestic(resticVersion string, withDefaultOptions bool) Prof
// Building initial set including generic sections (from data model)
profileSet := propertySetFromType(infoTypes.profile)
{
genericSection := propertySetFromType(infoTypes.genericSection)
for _, name := range infoTypes.genericSectionNames {
pi := new(propertyInfo)
pi.nested = &namedPropertySet{
propertySet: genericSection,
propertySet: propertySetFromType(infoTypes.genericSection),
name: name,
}
profileSet.properties[name] = pi
Expand Down
25 changes: 25 additions & 0 deletions config/info_customizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,31 @@ func init() {
}
})

// Profile: "stdout-hidden" default values
redirectOutput, noRedirectOutput := &RedirectOutputSection{StdoutFile: []string{""}}, &RedirectOutputSection{}
registerPropertyInfoCustomizer(func(sectionName, propertyName string, property accessibleProperty) {
if propertyName == constants.ParameterStdoutHidden {
property.basic().addDescriptionFilter(func(desc string) string {
var def string
if redirectOutput.IsOutputHidden(sectionName, false) == HideOutput {
def = `Default is "true" when redirected`
} else if noRedirectOutput.IsOutputHidden(sectionName, false) == HideOutput {
def = `Default is "true"`

Check warning on line 131 in config/info_customizer.go

View check run for this annotation

Codecov / codecov/patch

config/info_customizer.go#L131

Added line #L131 was not covered by tests
} else if noRedirectOutput.IsOutputHidden(sectionName, true) == HideOutput {
if sectionName == constants.CommandBackup {
def = `Default is "true" when "extended-status" is set`
} else {
def = `Default is "true" when "json" is requested`
}

Check warning on line 137 in config/info_customizer.go

View check run for this annotation

Codecov / codecov/patch

config/info_customizer.go#L133-L137

Added lines #L133 - L137 were not covered by tests
}
if def != "" {
desc = fmt.Sprintf("%s. %s", strings.TrimSuffix(strings.TrimSpace(desc), "."), def)
}
return desc
})
}
})

// Profile: exclude "help" (--help flag doesn't need to be in the reference)
ExcludeProfileProperty("*", "help")
}
31 changes: 31 additions & 0 deletions config/info_customizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,37 @@ func TestDeprecatedSection(t *testing.T) {
require.True(t, set.PropertyInfo("schedule").IsDeprecated())
}

func TestStdoutHiddenProperty(t *testing.T) {
var testType = struct {
RedirectOutputSection `mapstructure:",squash"`
}{}

var tests = []struct {
section string
expected string
}{
{section: constants.CommandCat, expected: `Default is "true" when redirected`},
{section: constants.CommandDump, expected: `Default is "true" when redirected`},
{section: constants.CommandCopy, expected: ``},
{section: constants.CommandBackup, expected: ``}, // Default is "true" when "extended-status" is set
}

for i, test := range tests {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
set := propertySetFromType(reflect.TypeOf(testType))
require.NotNil(t, set.PropertyInfo(constants.ParameterStdoutHidden))

base := set.PropertyInfo(constants.ParameterStdoutHidden).Description()
assert.NotEmpty(t, base)

customizeProperties(test.section, set.properties)
assert.Equal(t,
strings.TrimSuffix(strings.TrimSpace(fmt.Sprintf("%s. %s", base, test.expected)), "."),
set.PropertyInfo(constants.ParameterStdoutHidden).Description())
})
}
}

func TestHelpIsExcluded(t *testing.T) {
assert.True(t, isExcluded("*", "help"))
assert.False(t, isExcluded("*", "any-other"))
Expand Down
55 changes: 54 additions & 1 deletion config/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ type RunShellCommands interface {
GetRunShellCommands() *RunShellCommandsSection
}

type OutputHidden int

const (
ShowOutput = OutputHidden(iota)
HideJsonOutput
HideOutput
)

// RedirectOutput provides access to output redirection settings
type RedirectOutput interface {
IsOutputHidden(commandName string, jsonRequested bool) OutputHidden
IsOutputRedirected() bool
GetRedirectOutput() *RedirectOutputSection
}

// OtherFlags provides access to dynamic commandline flags
type OtherFlags interface {
GetOtherFlags() map[string]any
Expand Down Expand Up @@ -106,6 +121,7 @@ type Profile struct {
type GenericSection struct {
OtherFlagsSection `mapstructure:",squash"`
RunShellCommandsSection `mapstructure:",squash"`
RedirectOutputSection `mapstructure:",squash"`
}

func (g *GenericSection) IsEmpty() bool { return g == nil }
Expand Down Expand Up @@ -155,6 +171,7 @@ func (i *InitSection) getCommandFlags(profile *Profile) (flags *shell.Args) {
type BackupSection struct {
SectionWithScheduleAndMonitoring `mapstructure:",squash"`
RunShellCommandsSection `mapstructure:",squash"`
RedirectOutputSection `mapstructure:",squash"`
unresolvedSource []string
CheckBefore bool `mapstructure:"check-before" description:"Check the repository before starting the backup command"`
CheckAfter bool `mapstructure:"check-after" description:"Check the repository after the backup command succeeded"`
Expand All @@ -165,7 +182,7 @@ type BackupSection struct {
Iexclude []string `mapstructure:"iexclude" argument:"iexclude" argument-type:"no-glob"`
ExcludeFile []string `mapstructure:"exclude-file" argument:"exclude-file"`
FilesFrom []string `mapstructure:"files-from" argument:"files-from"`
ExtendedStatus bool `mapstructure:"extended-status" argument:"json"`
ExtendedStatus *bool `mapstructure:"extended-status" argument:"json"`
NoErrorOnWarning bool `mapstructure:"no-error-on-warning" description:"Do not fail the backup when some files could not be read"`
}

Expand All @@ -176,6 +193,10 @@ func (b *BackupSection) resolve(p *Profile) {
if len(b.StdinCommand) > 0 {
b.UseStdin = true
}
// Enable ExtendedStatus if unset and required for full functionality
if bools.IsUndefined(b.ExtendedStatus) && len(p.StatusFile)+len(p.PrometheusSaveToFile)+len(p.PrometheusPush) != 0 {
b.ExtendedStatus = bools.True()
}
// Resolve source paths
if b.unresolvedSource == nil {
b.unresolvedSource = b.Source
Expand Down Expand Up @@ -247,6 +268,7 @@ func (s *ScheduleBaseSection) GetSchedule() *ScheduleBaseSection { return s }
type CopySection struct {
SectionWithScheduleAndMonitoring `mapstructure:",squash"`
RunShellCommandsSection `mapstructure:",squash"`
RedirectOutputSection `mapstructure:",squash"`
Initialize bool `mapstructure:"initialize" description:"Initialize the secondary repository if missing"`
InitializeCopyChunkerParams *bool `mapstructure:"initialize-copy-chunker-params" default:"true" description:"Copy chunker parameters when initializing the secondary repository"`
Repository ConfidentialValue `mapstructure:"repository" description:"Destination repository to copy snapshots to"`
Expand Down Expand Up @@ -392,6 +414,37 @@ type SendMonitoringHeader struct {
Value ConfidentialValue `mapstructure:"value" examples:"\"Bearer ...\";\"Basic ...\";\"no-cache\";\"attachment;; filename=stats.txt\";\"application/json\";\"text/plain\";\"text/xml\"" description:"Value of the header"`
}

// RedirectOutputSection contains settings to redirect restic command output
type RedirectOutputSection struct {
StdoutHidden *bool `mapstructure:"stdout-hidden" description:"Toggles whether stdout is hidden in console & log"`
StdoutFile []string `mapstructure:"stdout-file" description:"Redirect restic stdout to file(s)"`
StdoutCommand []string `mapstructure:"stdout-command" description:"Pipe restic stdout to shell command(s)"`
}

func (r *RedirectOutputSection) GetRedirectOutput() *RedirectOutputSection { return r }

Check warning on line 424 in config/profile.go

View check run for this annotation

Codecov / codecov/patch

config/profile.go#L424

Added line #L424 was not covered by tests

func (r *RedirectOutputSection) IsOutputRedirected() bool {
return r != nil &&
(len(r.StdoutFile) > 0 || len(r.StdoutCommand) > 0)
}

func (r *RedirectOutputSection) IsOutputHidden(commandName string, jsonRequested bool) (hidden OutputHidden) {
if r != nil {
if bools.IsTrue(r.StdoutHidden) {
hidden = HideOutput

Check warning on line 434 in config/profile.go

View check run for this annotation

Codecov / codecov/patch

config/profile.go#L434

Added line #L434 was not covered by tests
} else if !bools.IsStrictlyFalse(r.StdoutHidden) {
if commandName == constants.CommandDump || commandName == constants.CommandCat {
if r.IsOutputRedirected() {
hidden = HideOutput
}
} else if commandName == constants.CommandBackup && jsonRequested {
hidden = HideJsonOutput
}
}
}
return
}

// OtherFlagsSection contains additional restic command line flags
type OtherFlagsSection struct {
OtherFlags map[string]any `mapstructure:",remain"`
Expand Down
1 change: 1 addition & 0 deletions constants/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
CommandSnapshots = "snapshots"
CommandUnlock = "unlock"
CommandMount = "mount"
CommandCat = "cat"
CommandCopy = "copy"
CommandDump = "dump"
CommandFind = "find"
Expand Down
1 change: 1 addition & 0 deletions constants/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ const (
ParameterPasswordFile = "password-file"
ParameterPasswordCommand = "password-command"
ParameterKeyHint = "key-hint"
ParameterStdoutHidden = "stdout-hidden"
)
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/creativeprojects/resticprofile/config"
"github.com/creativeprojects/resticprofile/constants"
"github.com/creativeprojects/resticprofile/filesearch"
"github.com/creativeprojects/resticprofile/monitor/console"
"github.com/creativeprojects/resticprofile/monitor/prom"
"github.com/creativeprojects/resticprofile/monitor/status"
"github.com/creativeprojects/resticprofile/preventsleep"
Expand Down Expand Up @@ -403,6 +404,7 @@ func runProfile(
}

// add progress receivers if necessary
wrapper.addProgress(console.NewProgress(profile))
if profile.StatusFile != "" {
wrapper.addProgress(status.NewProgress(profile, status.NewStatus(profile.StatusFile)))
}
Expand Down
Loading

0 comments on commit 117f6f8

Please sign in to comment.