Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite metadata #4573

Merged
merged 11 commits into from
Dec 24, 2023
2 changes: 1 addition & 1 deletion cmd/restic/cmd_repair_snapshots.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
changed, err := filterAndReplaceSnapshot(ctx, repo, sn,
func(ctx context.Context, sn *restic.Snapshot) (restic.ID, error) {
return rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
}, opts.DryRun, opts.Forget, "repaired")
}, opts.DryRun, opts.Forget, nil, "repaired")
if err != nil {
return errors.Fatalf("unable to rewrite snapshot ID %q: %v", sn.ID().Str(), err)
}
Expand Down
58 changes: 53 additions & 5 deletions cmd/restic/cmd_rewrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,25 @@ type SnapshotMetadataArgs struct {
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.
type RewriteOptions struct {
Forget bool
Expand All @@ -76,6 +95,10 @@ func init() {
f := cmdRewrite.Flags()
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.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)
initExcludePatternOptions(f, &rewriteOptions.excludePatternOptions)
Expand All @@ -91,6 +114,12 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
return false, err
}

metadata, err := opts.Metadata.convert()

if err != nil {
return false, err
}

selectByName := func(nodepath string) bool {
for _, reject := range rejectByNameFuncs {
if reject(nodepath) {
Expand All @@ -114,10 +143,10 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
return filterAndReplaceSnapshot(ctx, repo, sn,
func(ctx context.Context, sn *restic.Snapshot) (restic.ID, error) {
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)
repo.StartPackUploader(wgCtx, wg)
Expand Down Expand Up @@ -151,7 +180,7 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
return true, nil
}

if filteredTree == *sn.Tree {
if filteredTree == *sn.Tree && metadata == nil {
debug.Log("Snapshot %v not modified", sn)
return false, nil
}
Expand All @@ -164,6 +193,14 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
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)
MichaelEischer marked this conversation as resolved.
Show resolved Hide resolved
}

return true, nil
}

Expand All @@ -175,6 +212,17 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
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.
id, err := restic.SaveSnapshot(ctx, repo, sn)
if err != nil {
Expand All @@ -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 {
if opts.excludePatternOptions.Empty() {
return errors.Fatal("Nothing to do: no excludes provided")
if opts.excludePatternOptions.Empty() && opts.Metadata == nil {
return errors.Fatal("Nothing to do: no excludes provided and no new metadata provided")
}

repo, err := OpenRepository(ctx, gopts)
Expand Down
6 changes: 3 additions & 3 deletions cmd/restic/cmd_rewrite_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestRewrite(t *testing.T) {
createBasicRewriteRepo(t, env)

// 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)
rtest.Assert(t, len(snapshotIDs) == 2, "expected two snapshots, got %v", snapshotIDs)
testRunCheck(t, env.gopts)
Expand All @@ -51,7 +51,7 @@ func TestRewriteUnchanged(t *testing.T) {
snapshotID := createBasicRewriteRepo(t, env)

// 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)
rtest.Assert(t, len(newSnapshotIDs) == 1, "expected one snapshot, got %v", newSnapshotIDs)
rtest.Assert(t, snapshotID == newSnapshotIDs[0], "snapshot id changed unexpectedly")
Expand All @@ -64,7 +64,7 @@ func TestRewriteReplace(t *testing.T) {
snapshotID := createBasicRewriteRepo(t, env)

// 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)
rtest.Assert(t, len(newSnapshotIDs) == 1, "expected one snapshot, got %v", newSnapshotIDs)
rtest.Assert(t, snapshotID != newSnapshotIDs[0], "snapshot id should have changed")
Expand Down