2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-21 20:35:12 +00:00

rewrite: Implement rewriting metadata

This commit is contained in:
Gabriel Kabbe 2023-11-27 21:34:57 +01:00 committed by Michael Eischer
parent 7bf38b6c50
commit a02d8d75c2
3 changed files with 57 additions and 9 deletions

View File

@ -148,7 +148,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
changed, err := filterAndReplaceSnapshot(ctx, repo, sn, changed, err := filterAndReplaceSnapshot(ctx, repo, sn,
func(ctx context.Context, sn *restic.Snapshot) (restic.ID, error) { func(ctx context.Context, sn *restic.Snapshot) (restic.ID, error) {
return rewriter.RewriteTree(ctx, repo, "/", *sn.Tree) return rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
}, opts.DryRun, opts.Forget, "repaired") }, opts.DryRun, opts.Forget, nil, "repaired")
if err != nil { if err != nil {
return errors.Fatalf("unable to rewrite snapshot ID %q: %v", sn.ID().Str(), err) return errors.Fatalf("unable to rewrite snapshot ID %q: %v", sn.ID().Str(), err)
} }

View File

@ -57,6 +57,25 @@ type SnapshotMetadataArgs struct {
Time string Time string
} }
func (sma SnapshotMetadataArgs) convert() (*snapshotMetadata, error) {
if sma.Time == "" && sma.Hostname == "" {
return nil, nil
}
var timeStamp *time.Time
if sma.Time != "" {
t, err := time.ParseInLocation(TimeFormat, sma.Time, time.Local)
if err != nil {
return nil, errors.Fatalf("error in time option: %v\n", err)
}
timeStamp = &t
} else {
timeStamp = nil
}
return &snapshotMetadata{Hostname: sma.Hostname, Time: timeStamp}, nil
}
// RewriteOptions collects all options for the rewrite command. // RewriteOptions collects all options for the rewrite command.
type RewriteOptions struct { type RewriteOptions struct {
Forget bool Forget bool
@ -76,6 +95,10 @@ func init() {
f := cmdRewrite.Flags() f := cmdRewrite.Flags()
f.BoolVarP(&rewriteOptions.Forget, "forget", "", false, "remove original snapshots after creating new ones") f.BoolVarP(&rewriteOptions.Forget, "forget", "", false, "remove original snapshots after creating new ones")
f.BoolVarP(&rewriteOptions.DryRun, "dry-run", "n", false, "do not do anything, just print what would be done") f.BoolVarP(&rewriteOptions.DryRun, "dry-run", "n", false, "do not do anything, just print what would be done")
f.StringVar(&metadataOptions.Hostname, "new-host", "", "rewrite hostname")
f.StringVar(&metadataOptions.Time, "new-time", "", "rewrite time of the backup")
rewriteOptions.Metadata = &metadataOptions
initMultiSnapshotFilter(f, &rewriteOptions.SnapshotFilter, true) initMultiSnapshotFilter(f, &rewriteOptions.SnapshotFilter, true)
initExcludePatternOptions(f, &rewriteOptions.excludePatternOptions) initExcludePatternOptions(f, &rewriteOptions.excludePatternOptions)
@ -91,6 +114,12 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
return false, err return false, err
} }
metadata, err := opts.Metadata.convert()
if err != nil {
return false, err
}
selectByName := func(nodepath string) bool { selectByName := func(nodepath string) bool {
for _, reject := range rejectByNameFuncs { for _, reject := range rejectByNameFuncs {
if reject(nodepath) { if reject(nodepath) {
@ -114,10 +143,10 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
return filterAndReplaceSnapshot(ctx, repo, sn, return filterAndReplaceSnapshot(ctx, repo, sn,
func(ctx context.Context, sn *restic.Snapshot) (restic.ID, error) { func(ctx context.Context, sn *restic.Snapshot) (restic.ID, error) {
return rewriter.RewriteTree(ctx, repo, "/", *sn.Tree) return rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
}, opts.DryRun, opts.Forget, "rewrite") }, opts.DryRun, opts.Forget, metadata, "rewrite")
} }
func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *restic.Snapshot, filter func(ctx context.Context, sn *restic.Snapshot) (restic.ID, error), dryRun bool, forget bool, addTag string) (bool, error) { func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *restic.Snapshot, filter func(ctx context.Context, sn *restic.Snapshot) (restic.ID, error), dryRun bool, forget bool, metadata *snapshotMetadata, addTag string) (bool, error) {
wg, wgCtx := errgroup.WithContext(ctx) wg, wgCtx := errgroup.WithContext(ctx)
repo.StartPackUploader(wgCtx, wg) repo.StartPackUploader(wgCtx, wg)
@ -151,7 +180,7 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
return true, nil return true, nil
} }
if filteredTree == *sn.Tree { if filteredTree == *sn.Tree && metadata == nil {
debug.Log("Snapshot %v not modified", sn) debug.Log("Snapshot %v not modified", sn)
return false, nil return false, nil
} }
@ -164,6 +193,14 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
Verbosef("would remove old snapshot\n") Verbosef("would remove old snapshot\n")
} }
if metadata != nil && metadata.Time != nil {
Verbosef("would set time to %s\n", metadata.Time)
}
if metadata != nil && metadata.Hostname != "" {
Verbosef("would set time to %s\n", metadata.Hostname)
}
return true, nil return true, nil
} }
@ -175,6 +212,17 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
sn.AddTags([]string{addTag}) sn.AddTags([]string{addTag})
} }
if metadata != nil && metadata.Time != nil {
Verbosef("Setting time to %s\n", *metadata.Time)
sn.Time = *metadata.Time
}
if metadata != nil && metadata.Hostname != "" {
Verbosef("Setting host to %s\n", metadata.Hostname)
sn.Hostname = metadata.Hostname
}
// Save the new snapshot. // Save the new snapshot.
id, err := restic.SaveSnapshot(ctx, repo, sn) id, err := restic.SaveSnapshot(ctx, repo, sn)
if err != nil { if err != nil {
@ -194,8 +242,8 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
} }
func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, args []string) error { func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, args []string) error {
if opts.excludePatternOptions.Empty() { if opts.excludePatternOptions.Empty() && opts.Metadata == nil {
return errors.Fatal("Nothing to do: no excludes provided") return errors.Fatal("Nothing to do: no excludes provided and no new metadata provided")
} }
repo, err := OpenRepository(ctx, gopts) repo, err := OpenRepository(ctx, gopts)

View File

@ -39,7 +39,7 @@ func TestRewrite(t *testing.T) {
createBasicRewriteRepo(t, env) createBasicRewriteRepo(t, env)
// exclude some data // exclude some data
testRunRewriteExclude(t, env.gopts, []string{"3"}, false, nil) testRunRewriteExclude(t, env.gopts, []string{"3"}, false, &SnapshotMetadataArgs{Hostname: "", Time: ""})
snapshotIDs := testRunList(t, "snapshots", env.gopts) snapshotIDs := testRunList(t, "snapshots", env.gopts)
rtest.Assert(t, len(snapshotIDs) == 2, "expected two snapshots, got %v", snapshotIDs) rtest.Assert(t, len(snapshotIDs) == 2, "expected two snapshots, got %v", snapshotIDs)
testRunCheck(t, env.gopts) testRunCheck(t, env.gopts)
@ -51,7 +51,7 @@ func TestRewriteUnchanged(t *testing.T) {
snapshotID := createBasicRewriteRepo(t, env) snapshotID := createBasicRewriteRepo(t, env)
// use an exclude that will not exclude anything // use an exclude that will not exclude anything
testRunRewriteExclude(t, env.gopts, []string{"3dflkhjgdflhkjetrlkhjgfdlhkj"}, false, nil) testRunRewriteExclude(t, env.gopts, []string{"3dflkhjgdflhkjetrlkhjgfdlhkj"}, false, &SnapshotMetadataArgs{Hostname: "", Time: ""})
newSnapshotIDs := testRunList(t, "snapshots", env.gopts) newSnapshotIDs := testRunList(t, "snapshots", env.gopts)
rtest.Assert(t, len(newSnapshotIDs) == 1, "expected one snapshot, got %v", newSnapshotIDs) rtest.Assert(t, len(newSnapshotIDs) == 1, "expected one snapshot, got %v", newSnapshotIDs)
rtest.Assert(t, snapshotID == newSnapshotIDs[0], "snapshot id changed unexpectedly") rtest.Assert(t, snapshotID == newSnapshotIDs[0], "snapshot id changed unexpectedly")
@ -64,7 +64,7 @@ func TestRewriteReplace(t *testing.T) {
snapshotID := createBasicRewriteRepo(t, env) snapshotID := createBasicRewriteRepo(t, env)
// exclude some data // exclude some data
testRunRewriteExclude(t, env.gopts, []string{"3"}, true, nil) testRunRewriteExclude(t, env.gopts, []string{"3"}, true, &SnapshotMetadataArgs{Hostname: "", Time: ""})
newSnapshotIDs := testRunList(t, "snapshots", env.gopts) newSnapshotIDs := testRunList(t, "snapshots", env.gopts)
rtest.Assert(t, len(newSnapshotIDs) == 1, "expected one snapshot, got %v", newSnapshotIDs) rtest.Assert(t, len(newSnapshotIDs) == 1, "expected one snapshot, got %v", newSnapshotIDs)
rtest.Assert(t, snapshotID != newSnapshotIDs[0], "snapshot id should have changed") rtest.Assert(t, snapshotID != newSnapshotIDs[0], "snapshot id should have changed")