termstatus: add stdin and inject into backup command

This commit is contained in:
Michael Eischer
2025-09-18 22:17:21 +02:00
parent ca5b0c0249
commit 96af35555a
14 changed files with 73 additions and 41 deletions

View File

@@ -182,7 +182,7 @@ func filterExisting(items []string, warnf func(msg string, args ...interface{}))
// If filename is empty, readPatternsFromFile returns an empty slice.
// If filename is a dash (-), readPatternsFromFile will read the lines from the
// standard input.
func readLines(filename string) ([]string, error) {
func readLines(filename string, stdin io.ReadCloser) ([]string, error) {
if filename == "" {
return nil, nil
}
@@ -193,7 +193,7 @@ func readLines(filename string) ([]string, error) {
)
if filename == "-" {
data, err = io.ReadAll(os.Stdin)
data, err = io.ReadAll(stdin)
} else {
data, err = textfile.Read(filename)
}
@@ -218,8 +218,8 @@ func readLines(filename string) ([]string, error) {
// readFilenamesFromFileRaw reads a list of filenames from the given file,
// or stdin if filename is "-". Each filename is terminated by a zero byte,
// which is stripped off.
func readFilenamesFromFileRaw(filename string) (names []string, err error) {
f := os.Stdin
func readFilenamesFromFileRaw(filename string, stdin io.ReadCloser) (names []string, err error) {
var f io.ReadCloser = stdin
if filename != "-" {
if f, err = os.Open(filename); err != nil {
return nil, err
@@ -378,13 +378,13 @@ func collectRejectFuncs(opts BackupOptions, targets []string, fs fs.FS, warnf fu
}
// collectTargets returns a list of target files/dirs from several sources.
func collectTargets(opts BackupOptions, args []string, warnf func(msg string, args ...interface{})) (targets []string, err error) {
func collectTargets(opts BackupOptions, args []string, warnf func(msg string, args ...interface{}), stdin io.ReadCloser) (targets []string, err error) {
if opts.Stdin || opts.StdinCommand {
return nil, nil
}
for _, file := range opts.FilesFrom {
fromfile, err := readLines(file)
fromfile, err := readLines(file, stdin)
if err != nil {
return nil, err
}
@@ -409,7 +409,7 @@ func collectTargets(opts BackupOptions, args []string, warnf func(msg string, ar
}
for _, file := range opts.FilesFromVerbatim {
fromfile, err := readLines(file)
fromfile, err := readLines(file, stdin)
if err != nil {
return nil, err
}
@@ -422,7 +422,7 @@ func collectTargets(opts BackupOptions, args []string, warnf func(msg string, ar
}
for _, file := range opts.FilesFromRaw {
fromfile, err := readFilenamesFromFileRaw(file)
fromfile, err := readFilenamesFromFileRaw(file, stdin)
if err != nil {
return nil, err
}
@@ -490,7 +490,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
return err
}
targets, err := collectTargets(opts, args, msg.E)
targets, err := collectTargets(opts, args, msg.E, term.InputRaw())
if err != nil {
return err
}
@@ -582,7 +582,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
progressPrinter.V("read data from stdin")
}
filename := path.Join("/", opts.StdinFilename)
var source io.ReadCloser = os.Stdin
var source io.ReadCloser = term.InputRaw()
if opts.StdinCommand {
source, err = fs.NewCommandReader(ctx, args, msg.E)
if err != nil {

View File

@@ -67,7 +67,7 @@ func TestCollectTargets(t *testing.T) {
FilesFromRaw: []string{f3.Name()},
}
targets, err := collectTargets(opts, []string{filepath.Join(dir, "cmdline arg")}, t.Logf)
targets, err := collectTargets(opts, []string{filepath.Join(dir, "cmdline arg")}, t.Logf, nil)
rtest.OK(t, err)
sort.Strings(targets)
rtest.Equals(t, expect, targets)

View File

@@ -18,7 +18,7 @@ func testRunInit(t testing.TB, opts GlobalOptions) {
restic.TestSetLockTimeout(t, 0)
err := withTermStatus(opts, func(ctx context.Context, gopts GlobalOptions) error {
return runInit(ctx, InitOptions{}, opts, nil, gopts.term)
return runInit(ctx, InitOptions{}, gopts, nil, gopts.term)
})
rtest.OK(t, err)
t.Logf("repository initialized at %v", opts.Repo)
@@ -54,10 +54,18 @@ func TestInitCopyChunkerParams(t *testing.T) {
})
rtest.OK(t, err)
repo, err := OpenRepository(context.TODO(), env.gopts, &progress.NoopPrinter{})
var repo *repository.Repository
err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
repo, err = OpenRepository(ctx, gopts, &progress.NoopPrinter{})
return err
})
rtest.OK(t, err)
otherRepo, err := OpenRepository(context.TODO(), env2.gopts, &progress.NoopPrinter{})
var otherRepo *repository.Repository
err = withTermStatus(env2.gopts, func(ctx context.Context, gopts GlobalOptions) error {
otherRepo, err = OpenRepository(ctx, gopts, &progress.NoopPrinter{})
return err
})
rtest.OK(t, err)
rtest.Assert(t, repo.Config().ChunkerPolynomial == otherRepo.Config().ChunkerPolynomial,

View File

@@ -63,13 +63,16 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
})
rtest.OK(t, err)
repo, err := OpenRepository(context.TODO(), gopts, &progress.NoopPrinter{})
rtest.OK(t, err)
key, err := repository.SearchKey(context.TODO(), repo, testKeyNewPassword, 2, "")
rtest.OK(t, err)
_ = withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
repo, err := OpenRepository(ctx, gopts, &progress.NoopPrinter{})
rtest.OK(t, err)
key, err := repository.SearchKey(ctx, repo, testKeyNewPassword, 2, "")
rtest.OK(t, err)
rtest.Equals(t, "john", key.Username)
rtest.Equals(t, "example.com", key.Hostname)
rtest.Equals(t, "john", key.Username)
rtest.Equals(t, "example.com", key.Hostname)
return nil
})
}
func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {

View File

@@ -13,7 +13,7 @@ import (
func testRunList(t testing.TB, opts GlobalOptions, tpe string) restic.IDs {
buf, err := withCaptureStdout(opts, func(opts GlobalOptions) error {
return withTermStatus(opts, func(ctx context.Context, gopts GlobalOptions) error {
return runList(ctx, opts, []string{tpe}, gopts.term)
return runList(ctx, gopts, []string{tpe}, gopts.term)
})
})
rtest.OK(t, err)

View File

@@ -260,7 +260,7 @@ func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string, printe
err error
)
if terminal.StdinIsTerminal() {
if opts.term.InputIsTerminal() {
password, err = terminal.ReadPassword(ctx, os.Stdin, os.Stderr, prompt)
} else {
printer.PT("reading repository password from stdin")
@@ -286,7 +286,7 @@ func ReadPasswordTwice(ctx context.Context, gopts GlobalOptions, prompt1, prompt
if err != nil {
return "", err
}
if terminal.StdinIsTerminal() {
if gopts.term.InputIsTerminal() {
pw2, err := ReadPassword(ctx, gopts, prompt2, printer)
if err != nil {
return "", err
@@ -349,7 +349,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions, printer progress.Pr
}
passwordTriesLeft := 1
if terminal.StdinIsTerminal() && opts.password == "" && !opts.InsecureNoPassword {
if opts.term.InputIsTerminal() && opts.password == "" && !opts.InsecureNoPassword {
passwordTriesLeft = 3
}

View File

@@ -427,7 +427,7 @@ func withTermStatus(gopts GlobalOptions, callback func(ctx context.Context, gopt
ctx, cancel := context.WithCancel(context.TODO())
var wg sync.WaitGroup
term := termstatus.New(gopts.stdout, gopts.stderr, gopts.Quiet)
term := termstatus.New(os.Stdin, gopts.stdout, gopts.stderr, gopts.Quiet)
gopts.term = term
wg.Add(1)
go func() {

View File

@@ -178,7 +178,7 @@ func main() {
backends: collectBackends(),
}
func() {
term, cancel := termstatus.Setup(os.Stdout, os.Stderr, globalOptions.Quiet)
term, cancel := termstatus.Setup(os.Stdin, os.Stdout, os.Stderr, globalOptions.Quiet)
defer cancel()
globalOptions.stdout, globalOptions.stderr = termstatus.WrapStdio(term)
globalOptions.term = term

View File

@@ -131,12 +131,6 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
PasswordCommand: "notEmpty",
},
},
{
// Test must fail as no password is given.
Opts: secondaryRepoOptions{
Repo: "backupDst",
},
},
{
// Test must fail as current and legacy options are mixed
Opts: secondaryRepoOptions{