Skip to content

Commit 0041174

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 77b1f30 commit 0041174

4 files changed

Lines changed: 106 additions & 68 deletions

File tree

config/profile.go

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

360365
func (s *CopySection) IsEmpty() bool { return s == nil }
361366

367+
func (s *CopySection) IsCopyTo() bool {
368+
return (s.ToRepository.HasValue() || s.ToRepositoryFile != "") &&
369+
!(s.FromRepository.HasValue() || s.FromRepositoryFile != "")
370+
}
371+
362372
func (c *CopySection) resolve(p *Profile) {
363373
c.ScheduleBaseSection.resolve(p)
364374

365-
c.Repository.setValue(fixPath(c.Repository.Value(), expandEnv, expandUserHome))
375+
c.ToRepository.setValue(fixPath(c.ToRepository.Value(), expandEnv, expandUserHome))
366376
}
367377

368378
func (c *CopySection) setRootPath(p *Profile, rootPath string) {
369379
c.GenericSectionWithSchedule.setRootPath(p, rootPath)
370380

371-
c.PasswordFile = fixPath(c.PasswordFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
372-
c.RepositoryFile = fixPath(c.RepositoryFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
381+
c.ToPasswordFile = fixPath(c.ToPasswordFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
382+
c.ToRepositoryFile = fixPath(c.ToRepositoryFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
383+
c.FromPasswordFile = fixPath(c.FromPasswordFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
384+
c.FromRepositoryFile = fixPath(c.FromRepositoryFile, expandEnv, expandUserHome, absolutePrefix(rootPath))
373385
}
374386

375387
func (s *CopySection) getInitFlags(profile *Profile) *shell.Args {
376388
var init *InitSection
377389

378390
if s.InitializeCopyChunkerParams.IsTrueOrUndefined() {
379-
// Source repo for CopyChunkerParams
380-
init = &InitSection{
381-
CopyChunkerParams: true,
382-
FromKeyHint: profile.KeyHint,
383-
FromRepository: profile.Repository,
384-
FromRepositoryFile: profile.RepositoryFile,
385-
FromPasswordFile: profile.PasswordFile,
386-
FromPasswordCommand: profile.PasswordCommand,
387-
}
388-
init.OtherFlags = profile.OtherFlags
391+
if s.IsCopyTo() {
392+
// Source repo for CopyChunkerParams
393+
init = &InitSection{
394+
CopyChunkerParams: true,
395+
FromKeyHint: profile.KeyHint,
396+
FromRepository: profile.Repository,
397+
FromRepositoryFile: profile.RepositoryFile,
398+
FromPasswordFile: profile.PasswordFile,
399+
FromPasswordCommand: profile.PasswordCommand,
400+
}
401+
init.OtherFlags = profile.OtherFlags
402+
} else {
403+
init = &InitSection{
404+
CopyChunkerParams: true,
405+
FromKeyHint: s.FromKeyHint,
406+
FromRepository: s.FromRepository,
407+
FromRepositoryFile: s.FromRepositoryFile,
408+
FromPasswordFile: s.FromPasswordFile,
409+
FromPasswordCommand: s.FromPasswordCommand,
410+
}
411+
}
389412
} else {
390413
init = new(InitSection)
391414
}
392415

393-
// Repo that should be initialized
394416
ip := *profile
395-
ip.KeyHint = s.KeyHint
396-
ip.Repository = s.Repository
397-
ip.RepositoryFile = s.RepositoryFile
398-
ip.PasswordFile = s.PasswordFile
399-
ip.PasswordCommand = s.PasswordCommand
400-
ip.OtherFlags = s.OtherFlags
401-
417+
if s.IsCopyTo() {
418+
// Repo that should be initialized
419+
ip.KeyHint = s.ToKeyHint
420+
ip.Repository = s.ToRepository
421+
ip.RepositoryFile = s.ToRepositoryFile
422+
ip.PasswordFile = s.ToPasswordFile
423+
ip.PasswordCommand = s.ToPasswordCommand
424+
ip.OtherFlags = s.OtherFlags
425+
}
402426
return init.getCommandFlags(&ip)
403427
}
404428

405429
func (s *CopySection) getCommandFlags(profile *Profile) (flags *shell.Args) {
406430
repositoryArgs := map[string]string{
407-
constants.ParameterRepository: s.Repository.Value(),
408-
constants.ParameterRepositoryFile: s.RepositoryFile,
409-
constants.ParameterPasswordFile: s.PasswordFile,
410-
constants.ParameterPasswordCommand: s.PasswordCommand,
411-
constants.ParameterKeyHint: s.KeyHint,
431+
constants.ParameterRepository: s.ToRepository.Value(),
432+
constants.ParameterRepositoryFile: s.ToRepositoryFile,
433+
constants.ParameterPasswordFile: s.ToPasswordFile,
434+
constants.ParameterPasswordCommand: s.ToPasswordCommand,
435+
constants.ParameterKeyHint: s.ToKeyHint,
436+
constants.ParameterFromRepository: s.FromRepository.Value(),
437+
constants.ParameterFromRepositoryFile: s.FromRepositoryFile,
438+
constants.ParameterFromPasswordFile: s.FromPasswordFile,
439+
constants.ParameterFromPasswordCommand: s.FromPasswordCommand,
440+
constants.ParameterFromKeyHint: s.FromKeyHint,
412441
}
413442

414443
// Handle confidential repo in flags
415-
restore := profile.replaceWithRepositoryFile(&s.Repository, &s.RepositoryFile, "-to")
416-
defer restore()
444+
restore1 := profile.replaceWithRepositoryFile(&s.ToRepository, &s.ToRepositoryFile, "-to")
445+
defer restore1()
446+
restore2 := profile.replaceWithRepositoryFile(&s.FromRepository, &s.FromRepositoryFile, "-from")
447+
defer restore2()
417448

418449
flags = profile.GetCommonFlags()
419450
addArgsFromStruct(flags, s)
@@ -427,9 +458,11 @@ func (s *CopySection) getCommandFlags(profile *Profile) (flags *shell.Args) {
427458
}
428459
}
429460
} else {
430-
// restic >= 0.14: from-repo, from-password-file, etc. is the source, repo, password-file, etc. the destination
431-
for name := range maps.Keys(repositoryArgs) {
432-
flags.Rename(name, fmt.Sprintf("from-%s", name))
461+
if s.IsCopyTo() {
462+
// restic >= 0.14: from-repo, from-password-file, etc. is the source, repo, password-file, etc. the destination
463+
for name := range maps.Keys(repositoryArgs) {
464+
flags.Rename(name, fmt.Sprintf("from-%s", name))
465+
}
433466
}
434467
for name, value := range repositoryArgs {
435468
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)
@@ -1503,11 +1503,11 @@ func TestGetInitStructFields(t *testing.T) {
15031503

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

15131513
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)