feat: allow override env RESTIC_HOST with flag to filter all snapshots (#5541)

This commit is contained in:
Srigovind Nayak
2025-10-05 13:22:50 +02:00
committed by GitHub
parent a2a49cf784
commit 22f254c9ca
16 changed files with 98 additions and 13 deletions
+1
View File
@@ -49,6 +49,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runCopy(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
},
}
+1
View File
@@ -48,6 +48,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runDump(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
},
}
+1
View File
@@ -52,6 +52,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runFind(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
},
}
+1
View File
@@ -50,6 +50,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runForget(cmd.Context(), opts, pruneOpts, *globalOptions, globalOptions.term, args)
},
}
+34 -3
View File
@@ -96,7 +96,38 @@ func TestForgetOptionValues(t *testing.T) {
func TestForgetHostnameDefaulting(t *testing.T) {
t.Setenv("RESTIC_HOST", "testhost")
opts := ForgetOptions{}
opts.AddFlags(pflag.NewFlagSet("test", pflag.ContinueOnError))
rtest.Equals(t, []string{"testhost"}, opts.Hosts)
tests := []struct {
name string
args []string
want []string
}{
{
name: "env default when flag not set",
args: nil,
want: []string{"testhost"},
},
{
name: "flag overrides env",
args: []string{"--host", "flaghost"},
want: []string{"flaghost"},
},
{
name: "empty flag clears env",
args: []string{"--host", ""},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
set := pflag.NewFlagSet(tt.name, pflag.ContinueOnError)
opts := ForgetOptions{}
opts.AddFlags(set)
err := set.Parse(tt.args)
rtest.Assert(t, err == nil, "expected no error for input")
finalizeSnapshotFilter(&opts.SnapshotFilter)
rtest.Equals(t, tt.want, opts.Hosts)
})
}
}
+1
View File
@@ -61,6 +61,7 @@ Exit status is 12 if the password is incorrect.
DisableAutoGenTag: true,
GroupID: cmdGroupDefault,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runLs(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
},
}
+1
View File
@@ -82,6 +82,7 @@ Exit status is 12 if the password is incorrect.
DisableAutoGenTag: true,
GroupID: cmdGroupDefault,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runMount(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
},
}
+1
View File
@@ -51,6 +51,7 @@ Exit status is 12 if the password is incorrect.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runRepairSnapshots(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
},
}
+1
View File
@@ -46,6 +46,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runRestore(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
},
}
+1
View File
@@ -61,6 +61,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runRewrite(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
},
}
+1
View File
@@ -37,6 +37,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runSnapshots(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
},
}
+1
View File
@@ -64,6 +64,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runStats(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
},
}
+2
View File
@@ -40,6 +40,8 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
finalizeSnapshotFilter(&opts.SnapshotFilter)
finalizeSnapshotFilter(&opts.SnapshotFilter)
return runTag(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
},
}
+18 -10
View File
@@ -12,31 +12,39 @@ import (
// initMultiSnapshotFilter is used for commands that work on multiple snapshots
// MUST be combined with restic.FindFilteredSnapshots or FindFilteredSnapshots
// MUST be followed by finalizeSnapshotFilter after flag parsing
func initMultiSnapshotFilter(flags *pflag.FlagSet, filt *data.SnapshotFilter, addHostShorthand bool) {
hostShorthand := "H"
if !addHostShorthand {
hostShorthand = ""
}
flags.StringArrayVarP(&filt.Hosts, "host", hostShorthand, nil, "only consider snapshots for this `host` (can be specified multiple times) (default: $RESTIC_HOST)")
flags.StringArrayVarP(&filt.Hosts, "host", hostShorthand, nil, "only consider snapshots for this `host` (can be specified multiple times, use empty string to unset default value) (default: $RESTIC_HOST)")
flags.Var(&filt.Tags, "tag", "only consider snapshots including `tag[,tag,...]` (can be specified multiple times)")
flags.StringArrayVar(&filt.Paths, "path", nil, "only consider snapshots including this (absolute) `path` (can be specified multiple times, snapshots must include all specified paths)")
// set default based on env if set
if host := os.Getenv("RESTIC_HOST"); host != "" {
filt.Hosts = []string{host}
}
}
// initSingleSnapshotFilter is used for commands that work on a single snapshot
// MUST be combined with restic.FindFilteredSnapshot
// MUST be followed by finalizeSnapshotFilter after flag parsing
func initSingleSnapshotFilter(flags *pflag.FlagSet, filt *data.SnapshotFilter) {
flags.StringArrayVarP(&filt.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when snapshot ID \"latest\" is given (can be specified multiple times) (default: $RESTIC_HOST)")
flags.StringArrayVarP(&filt.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when snapshot ID \"latest\" is given (can be specified multiple times, use empty string to unset default value) (default: $RESTIC_HOST)")
flags.Var(&filt.Tags, "tag", "only consider snapshots including `tag[,tag,...]`, when snapshot ID \"latest\" is given (can be specified multiple times)")
flags.StringArrayVar(&filt.Paths, "path", nil, "only consider snapshots including this (absolute) `path`, when snapshot ID \"latest\" is given (can be specified multiple times, snapshots must include all specified paths)")
}
// set default based on env if set
if host := os.Getenv("RESTIC_HOST"); host != "" {
filt.Hosts = []string{host}
// finalizeSnapshotFilter applies RESTIC_HOST default only if --host flag wasn't explicitly set.
// This allows users to override RESTIC_HOST by providing --host="" or --host with explicit values.
func finalizeSnapshotFilter(filt *data.SnapshotFilter) {
// Only apply RESTIC_HOST default if the --host flag wasn't changed by the user
if filt.Hosts == nil {
if host := os.Getenv("RESTIC_HOST"); host != "" {
filt.Hosts = []string{host}
}
}
// If flag was set to empty string explicitly (e.g., --host=""),
// filt.Hosts will be []string{""} which should be cleaned up to allow all hosts
if len(filt.Hosts) == 1 && filt.Hosts[0] == "" {
filt.Hosts = nil
}
}
+21
View File
@@ -39,6 +39,24 @@ func TestSnapshotFilter(t *testing.T) {
[]string{"abc"},
"def",
},
{
"env set, empty flag overrides",
[]string{"--host", ""},
nil, // empty host filter means all hosts
"envhost",
},
{
"env set, multiple flags override",
[]string{"--host", "host1", "--host", "host2"},
[]string{"host1", "host2"},
"envhost",
},
{
"env set, multiple hosts including empty",
[]string{"--host", "host1", "--host", ""},
[]string{"host1", ""},
"envhost",
},
} {
t.Run(test.name, func(t *testing.T) {
t.Setenv("RESTIC_HOST", test.env)
@@ -54,6 +72,9 @@ func TestSnapshotFilter(t *testing.T) {
err := set.Parse(test.args)
rtest.OK(t, err)
// Apply the finalization logic to handle env defaults
finalizeSnapshotFilter(flt)
rtest.Equals(t, test.expected, flt.Hosts, "unexpected hosts")
}
})