github.com/mckael/restic@v0.8.3/cmd/restic/cmd_tag.go (about) 1 package main 2 3 import ( 4 "context" 5 6 "github.com/spf13/cobra" 7 8 "github.com/restic/restic/internal/debug" 9 "github.com/restic/restic/internal/errors" 10 "github.com/restic/restic/internal/repository" 11 "github.com/restic/restic/internal/restic" 12 ) 13 14 var cmdTag = &cobra.Command{ 15 Use: "tag [flags] [snapshot-ID ...]", 16 Short: "Modify tags on snapshots", 17 Long: ` 18 The "tag" command allows you to modify tags on exiting snapshots. 19 20 You can either set/replace the entire set of tags on a snapshot, or 21 add tags to/remove tags from the existing set. 22 23 When no snapshot-ID is given, all snapshots matching the host, tag and path filter criteria are modified. 24 `, 25 DisableAutoGenTag: true, 26 RunE: func(cmd *cobra.Command, args []string) error { 27 return runTag(tagOptions, globalOptions, args) 28 }, 29 } 30 31 // TagOptions bundles all options for the 'tag' command. 32 type TagOptions struct { 33 Host string 34 Paths []string 35 Tags restic.TagLists 36 SetTags []string 37 AddTags []string 38 RemoveTags []string 39 } 40 41 var tagOptions TagOptions 42 43 func init() { 44 cmdRoot.AddCommand(cmdTag) 45 46 tagFlags := cmdTag.Flags() 47 tagFlags.StringSliceVar(&tagOptions.SetTags, "set", nil, "`tag` which will replace the existing tags (can be given multiple times)") 48 tagFlags.StringSliceVar(&tagOptions.AddTags, "add", nil, "`tag` which will be added to the existing tags (can be given multiple times)") 49 tagFlags.StringSliceVar(&tagOptions.RemoveTags, "remove", nil, "`tag` which will be removed from the existing tags (can be given multiple times)") 50 51 tagFlags.StringVarP(&tagOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given") 52 tagFlags.Var(&tagOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot-ID is given") 53 tagFlags.StringArrayVar(&tagOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given") 54 } 55 56 func changeTags(ctx context.Context, repo *repository.Repository, sn *restic.Snapshot, setTags, addTags, removeTags []string) (bool, error) { 57 var changed bool 58 59 if len(setTags) != 0 { 60 // Setting the tag to an empty string really means no tags. 61 if len(setTags) == 1 && setTags[0] == "" { 62 setTags = nil 63 } 64 sn.Tags = setTags 65 changed = true 66 } else { 67 changed = sn.AddTags(addTags) 68 if sn.RemoveTags(removeTags) { 69 changed = true 70 } 71 } 72 73 if changed { 74 // Retain the original snapshot id over all tag changes. 75 if sn.Original == nil { 76 sn.Original = sn.ID() 77 } 78 79 // Save the new snapshot. 80 id, err := repo.SaveJSONUnpacked(ctx, restic.SnapshotFile, sn) 81 if err != nil { 82 return false, err 83 } 84 85 debug.Log("new snapshot saved as %v", id) 86 87 if err = repo.Flush(ctx); err != nil { 88 return false, err 89 } 90 91 // Remove the old snapshot. 92 h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()} 93 if err = repo.Backend().Remove(ctx, h); err != nil { 94 return false, err 95 } 96 97 debug.Log("old snapshot %v removed", sn.ID()) 98 } 99 return changed, nil 100 } 101 102 func runTag(opts TagOptions, gopts GlobalOptions, args []string) error { 103 if len(opts.SetTags) == 0 && len(opts.AddTags) == 0 && len(opts.RemoveTags) == 0 { 104 return errors.Fatal("nothing to do!") 105 } 106 if len(opts.SetTags) != 0 && (len(opts.AddTags) != 0 || len(opts.RemoveTags) != 0) { 107 return errors.Fatal("--set and --add/--remove cannot be given at the same time") 108 } 109 110 repo, err := OpenRepository(gopts) 111 if err != nil { 112 return err 113 } 114 115 if !gopts.NoLock { 116 Verbosef("create exclusive lock for repository\n") 117 lock, err := lockRepoExclusive(repo) 118 defer unlockRepo(lock) 119 if err != nil { 120 return err 121 } 122 } 123 124 changeCnt := 0 125 ctx, cancel := context.WithCancel(gopts.ctx) 126 defer cancel() 127 for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) { 128 changed, err := changeTags(ctx, repo, sn, opts.SetTags, opts.AddTags, opts.RemoveTags) 129 if err != nil { 130 Warnf("unable to modify the tags for snapshot ID %q, ignoring: %v\n", sn.ID(), err) 131 continue 132 } 133 if changed { 134 changeCnt++ 135 } 136 } 137 if changeCnt == 0 { 138 Verbosef("no snapshots were modified\n") 139 } else { 140 Verbosef("modified tags on %v snapshots\n", changeCnt) 141 } 142 return nil 143 }