Skip to content

Commit a7ddb09

Browse files
committed
Allow standard copy in addition to the "special case"
resticprofile implements a "special case" for the copy command, which does not use the "from-"-prefix and reverses the copy direction. With this change the standard direction is used when the "from" prefix is explicitly used.
1 parent e991cfd commit a7ddb09

4 files changed

Lines changed: 105 additions & 67 deletions

File tree

config/profile.go

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -345,71 +345,102 @@ type CopySection struct {
345345
RunShellCommandsSection `mapstructure:",squash"`
346346
Initialize bool `mapstructure:"initialize" description:"Initialize the secondary repository if missing"`
347347
InitializeCopyChunkerParams maybe.Bool `mapstructure:"initialize-copy-chunker-params" default:"true" description:"Copy chunker parameters when initializing the secondary repository"`
348-
Repository ConfidentialValue `mapstructure:"repository" description:"Destination repository to copy snapshots to"`
349-
RepositoryFile string `mapstructure:"repository-file" description:"File from which to read the destination repository location to copy snapshots to"`
350-
PasswordFile string `mapstructure:"password-file" description:"File to read the destination repository password from"`
351-
PasswordCommand string `mapstructure:"password-command" description:"Shell command to obtain the destination repository password from"`
352-
KeyHint string `mapstructure:"key-hint" description:"Key ID of key to try decrypting the destination repository first"`
348+
FromPasswordFile string `mapstructure:"from-password-file" description:"File to read the source repository password from"`
349+
FromPasswordCommand string `mapstructure:"from-password-command" description:"Shell command to obtain the source repository password from"`
350+
FromRepository ConfidentialValue `mapstructure:"from-repository" description:"Source repository to copy snapshots to"`
351+
FromRepositoryFile string `mapstructure:"from-repository-file" description:"File from which to read the source repository location to copy snapshots to"`
352+
FromKeyHint string `mapstructure:"from-key-hint" description:"Key ID of key to try decrypting the source repository first"`
353+
ToPasswordFile string `mapstructure:"password-file" description:"File to read the destination repository password from"`
354+
ToPasswordCommand string `mapstructure:"password-command" description:"Shell command to obtain the destination repository password from"`
355+
ToRepository ConfidentialValue `mapstructure:"repository" description:"Destination repository to copy snapshots to"`
356+
ToRepositoryFile string `mapstructure:"repository-file" description:"File from which to read the destination repository location to copy snapshots to"`
357+
ToKeyHint string `mapstructure:"key-hint" description:"Key ID of key to try decrypting the destination repository first"`
353358
Snapshots []string `mapstructure:"snapshot" description:"Snapshot IDs to copy (if empty, all snapshots are copied)"`
354359
}
355360

356361
func (s *CopySection) IsEmpty() bool { return s == nil }
357362

363+
func (s *CopySection) IsCopyTo() bool {
364+
return (s.ToRepository.HasValue() || s.ToRepositoryFile != "") &&
365+
!(s.FromRepository.HasValue() || s.FromRepositoryFile != "")
366+
}
367+
358368
func (c *CopySection) resolve(p *Profile) {
359369
c.ScheduleBaseSection.resolve(p)
360370

361-
c.Repository.setValue(fixPath(c.Repository.Value(), expandEnv, expandUserHome))
371+
c.ToRepository.setValue(fixPath(c.ToRepository.Value(), expandEnv, expandUserHome))
362372
}
363373

364374
func (c *CopySection) setRootPath(p *Profile, rootPath string) {
365375
c.SectionWithScheduleAndMonitoring.setRootPath(p, rootPath)
366376

367-
c.PasswordFile = fixPath(c.PasswordFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
368-
c.RepositoryFile = fixPath(c.RepositoryFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
377+
c.ToPasswordFile = fixPath(c.ToPasswordFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
378+
c.ToRepositoryFile = fixPath(c.ToRepositoryFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
379+
c.FromPasswordFile = fixPath(c.FromPasswordFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
380+
c.FromRepositoryFile = fixPath(c.FromRepositoryFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
369381
}
370382

371383
func (s *CopySection) getInitFlags(profile *Profile) *shell.Args {
372384
var init *InitSection
373385

374386
if s.InitializeCopyChunkerParams.IsTrueOrUndefined() {
375-
// Source repo for CopyChunkerParams
376-
init = &InitSection{
377-
CopyChunkerParams: true,
378-
FromKeyHint: profile.KeyHint,
379-
FromRepository: profile.Repository,
380-
FromRepositoryFile: profile.RepositoryFile,
381-
FromPasswordFile: profile.PasswordFile,
382-
FromPasswordCommand: profile.PasswordCommand,
387+
if s.IsCopyTo() {
388+
// Source repo for CopyChunkerParams
389+
init = &InitSection{
390+
CopyChunkerParams: true,
391+
FromKeyHint: profile.KeyHint,
392+
FromRepository: profile.Repository,
393+
FromRepositoryFile: profile.RepositoryFile,
394+
FromPasswordFile: profile.PasswordFile,
395+
FromPasswordCommand: profile.PasswordCommand,
396+
}
397+
init.OtherFlags = profile.OtherFlags
398+
} else {
399+
init = &InitSection{
400+
CopyChunkerParams: true,
401+
FromKeyHint: s.FromKeyHint,
402+
FromRepository: s.FromRepository,
403+
FromRepositoryFile: s.FromRepositoryFile,
404+
FromPasswordFile: s.FromPasswordFile,
405+
FromPasswordCommand: s.FromPasswordCommand,
406+
}
383407
}
384-
init.OtherFlags = profile.OtherFlags
385408
} else {
386409
init = new(InitSection)
387410
}
388411

389-
// Repo that should be initialized
390412
ip := *profile
391-
ip.KeyHint = s.KeyHint
392-
ip.Repository = s.Repository
393-
ip.RepositoryFile = s.RepositoryFile
394-
ip.PasswordFile = s.PasswordFile
395-
ip.PasswordCommand = s.PasswordCommand
396-
ip.OtherFlags = s.OtherFlags
397-
413+
if s.IsCopyTo() {
414+
// Repo that should be initialized
415+
ip.KeyHint = s.ToKeyHint
416+
ip.Repository = s.ToRepository
417+
ip.RepositoryFile = s.ToRepositoryFile
418+
ip.PasswordFile = s.ToPasswordFile
419+
ip.PasswordCommand = s.ToPasswordCommand
420+
ip.OtherFlags = s.OtherFlags
421+
}
398422
return init.getCommandFlags(&ip)
399423
}
400424

401425
func (s *CopySection) getCommandFlags(profile *Profile) (flags *shell.Args) {
402426
repositoryArgs := map[string]string{
403-
constants.ParameterRepository: s.Repository.Value(),
404-
constants.ParameterRepositoryFile: s.RepositoryFile,
405-
constants.ParameterPasswordFile: s.PasswordFile,
406-
constants.ParameterPasswordCommand: s.PasswordCommand,
407-
constants.ParameterKeyHint: s.KeyHint,
427+
constants.ParameterRepository: s.ToRepository.Value(),
428+
constants.ParameterRepositoryFile: s.ToRepositoryFile,
429+
constants.ParameterPasswordFile: s.ToPasswordFile,
430+
constants.ParameterPasswordCommand: s.ToPasswordCommand,
431+
constants.ParameterKeyHint: s.ToKeyHint,
432+
constants.ParameterFromRepository: s.FromRepository.Value(),
433+
constants.ParameterFromRepositoryFile: s.FromRepositoryFile,
434+
constants.ParameterFromPasswordFile: s.FromPasswordFile,
435+
constants.ParameterFromPasswordCommand: s.FromPasswordCommand,
436+
constants.ParameterFromKeyHint: s.FromKeyHint,
408437
}
409438

410439
// Handle confidential repo in flags
411-
restore := profile.replaceWithRepositoryFile(&s.Repository, &s.RepositoryFile, "-to")
412-
defer restore()
440+
restore1 := profile.replaceWithRepositoryFile(&s.ToRepository, &s.ToRepositoryFile, "-to")
441+
defer restore1()
442+
restore2 := profile.replaceWithRepositoryFile(&s.FromRepository, &s.FromRepositoryFile, "-from")
443+
defer restore2()
413444

414445
flags = profile.GetCommonFlags()
415446
addArgsFromStruct(flags, s)
@@ -423,9 +454,11 @@ func (s *CopySection) getCommandFlags(profile *Profile) (flags *shell.Args) {
423454
}
424455
}
425456
} else {
426-
// restic >= 0.14: from-repo, from-password-file, etc. is the source, repo, password-file, etc. the destination
427-
for name := range maps.Keys(repositoryArgs) {
428-
flags.Rename(name, fmt.Sprintf("from-%s", name))
457+
if s.IsCopyTo() {
458+
// restic >= 0.14: from-repo, from-password-file, etc. is the source, repo, password-file, etc. the destination
459+
for name := range maps.Keys(repositoryArgs) {
460+
flags.Rename(name, fmt.Sprintf("from-%s", name))
461+
}
429462
}
430463
for name, value := range repositoryArgs {
431464
if len(value) > 0 {

config/profile_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ func TestEnvironmentInProfileRepo(t *testing.T) {
313313
profile.ResolveConfiguration()
314314
assert.Equal(t, repoPath, filepath.ToSlash(profile.Repository.Value()))
315315
assert.Equal(t, repoPath, filepath.ToSlash(profile.Init.FromRepository.Value()))
316-
assert.Equal(t, repoPath, filepath.ToSlash(profile.Copy.Repository.Value()))
316+
assert.Equal(t, repoPath, filepath.ToSlash(profile.Copy.ToRepository.Value()))
317317

318318
profile.SetRootPath("any")
319319
assert.Equal(t, repoPath+".key", filepath.ToSlash(profile.PasswordFile))
@@ -433,7 +433,7 @@ from-password-file = "key"
433433
assert.ElementsMatch(t, []string{"/wd/include-verbatim"}, profile.Backup.FilesFromVerbatim)
434434
assert.ElementsMatch(t, []string{"exclude"}, profile.Backup.Exclude)
435435
assert.ElementsMatch(t, []string{"iexclude"}, profile.Backup.Iexclude)
436-
assert.Equal(t, "/wd/key", profile.Copy.PasswordFile)
436+
assert.Equal(t, "/wd/key", profile.Copy.ToPasswordFile)
437437
assert.Equal(t, []string{"/wd/key"}, profile.OtherSections[constants.CommandDump].OtherFlags["password-file"])
438438
assert.Equal(t, "/wd/key", profile.Init.FromPasswordFile)
439439
assert.Equal(t, "/wd/key", profile.Init.FromRepositoryFile)
@@ -1502,11 +1502,11 @@ func TestGetInitStructFields(t *testing.T) {
15021502

15031503
func TestGetCopyStructFields(t *testing.T) {
15041504
copySection := &CopySection{
1505-
Repository: NewConfidentialValue("dest-repo"),
1506-
RepositoryFile: "dest-repo-file",
1507-
PasswordFile: "dest-pw-file",
1508-
PasswordCommand: "dest-pw-command",
1509-
KeyHint: "dest-key-hint",
1505+
ToRepository: NewConfidentialValue("dest-repo"),
1506+
ToRepositoryFile: "dest-repo-file",
1507+
ToPasswordFile: "dest-pw-file",
1508+
ToPasswordCommand: "dest-pw-command",
1509+
ToKeyHint: "dest-key-hint",
15101510
}
15111511

15121512
copySection.OtherFlags = map[string]any{"option": "opt=dest"}

constants/parameter.go

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,32 @@ package constants
22

33
// Parameter
44
const (
5-
ParameterIONice = "ionice"
6-
ParameterIONiceClass = "ionice-class"
7-
ParameterIONiceLevel = "ionice-level"
8-
ParameterNice = "nice"
9-
ParameterPriority = "priority"
10-
ParameterDefaultCommand = "default-command"
11-
ParameterInitialize = "initialize"
12-
ParameterResticBinary = "restic-binary"
13-
ParameterInherit = "inherit"
14-
ParameterHost = "host"
15-
ParameterPath = "path"
16-
ParameterTag = "tag"
17-
ParameterKeepTag = "keep-tag"
18-
ParameterGroupBy = "group-by"
19-
ParameterVerbose = "verbose"
20-
ParameterDescription = "description"
21-
ParameterVersion = "version"
22-
ParameterRetryLock = "retry-lock"
23-
ParameterRepository = "repo"
24-
ParameterRepositoryFile = "repository-file"
25-
ParameterPasswordFile = "password-file"
26-
ParameterPasswordCommand = "password-command"
27-
ParameterKeyHint = "key-hint"
5+
ParameterIONice = "ionice"
6+
ParameterIONiceClass = "ionice-class"
7+
ParameterIONiceLevel = "ionice-level"
8+
ParameterNice = "nice"
9+
ParameterPriority = "priority"
10+
ParameterDefaultCommand = "default-command"
11+
ParameterInitialize = "initialize"
12+
ParameterResticBinary = "restic-binary"
13+
ParameterInherit = "inherit"
14+
ParameterHost = "host"
15+
ParameterPath = "path"
16+
ParameterTag = "tag"
17+
ParameterKeepTag = "keep-tag"
18+
ParameterGroupBy = "group-by"
19+
ParameterVerbose = "verbose"
20+
ParameterDescription = "description"
21+
ParameterVersion = "version"
22+
ParameterRetryLock = "retry-lock"
23+
ParameterRepository = "repo"
24+
ParameterRepositoryFile = "repository-file"
25+
ParameterPasswordFile = "password-file"
26+
ParameterPasswordCommand = "password-command"
27+
ParameterKeyHint = "key-hint"
28+
ParameterFromRepository = "from-repo"
29+
ParameterFromRepositoryFile = "from-repository-file"
30+
ParameterFromPasswordFile = "from-password-file"
31+
ParameterFromPasswordCommand = "from-password-command"
32+
ParameterFromKeyHint = "from-key-hint"
2833
)

wrapper_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,8 +1630,8 @@ func TestRunInitCopyCommand(t *testing.T) {
16301630
PasswordFile: "password_origin",
16311631
Copy: &config.CopySection{
16321632
InitializeCopyChunkerParams: copyChunkerParams,
1633-
Repository: config.NewConfidentialValue("repo_copy"),
1634-
PasswordFile: "password_copy",
1633+
ToRepository: config.NewConfidentialValue("repo_copy"),
1634+
ToPasswordFile: "password_copy",
16351635
},
16361636
}
16371637
require.NoError(t, p.SetResticVersion(resticVersion))

0 commit comments

Comments
 (0)