github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/snap/cmd_snapshot.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "fmt" 24 "strconv" 25 "strings" 26 27 "github.com/jessevdk/go-flags" 28 29 "github.com/snapcore/snapd/i18n" 30 "github.com/snapcore/snapd/strutil" 31 "github.com/snapcore/snapd/strutil/quantity" 32 ) 33 34 func fmtSize(size int64) string { 35 return quantity.FormatAmount(uint64(size), -1) + "B" 36 } 37 38 var ( 39 shortSavedHelp = i18n.G("List currently stored snapshots") 40 shortSaveHelp = i18n.G("Save a snapshot of the current data") 41 shortForgetHelp = i18n.G("Delete a snapshot") 42 shortCheckHelp = i18n.G("Check a snapshot") 43 shortRestoreHelp = i18n.G("Restore a snapshot") 44 ) 45 46 var longSavedHelp = i18n.G(` 47 The saved command displays a list of snapshots that have been created 48 previously with the 'save' command. 49 `) 50 var longSaveHelp = i18n.G(` 51 The save command creates a snapshot of the current user, system and 52 configuration data for the given snaps. 53 54 By default, this command saves the data of all snaps for all users. 55 Alternatively, you can specify the data of which snaps to save, or 56 for which users, or a combination of these. 57 58 If a snap is included in a save operation, excluding its system and 59 configuration data from the snapshot is not currently possible. This 60 restriction may be lifted in the future. 61 `) 62 var longForgetHelp = i18n.G(` 63 The forget command deletes a snapshot. This operation can not be 64 undone. 65 66 A snapshot contains archives for the user, system and configuration 67 data of each snap included in the snapshot. 68 69 By default, this command forgets all the data in a snapshot. 70 Alternatively, you can specify the data of which snaps to forget. 71 `) 72 var longCheckHelp = i18n.G(` 73 The check-snapshot command verifies the user, system and configuration 74 data of the snaps included in the specified snapshot. 75 76 The check operation runs the same data integrity verification that is 77 performed when a snapshot is restored. 78 79 By default, this command checks all the data in a snapshot. 80 Alternatively, you can specify the data of which snaps to check, or 81 for which users, or a combination of these. 82 83 If a snap is included in a check-snapshot operation, excluding its 84 system and configuration data from the check is not currently 85 possible. This restriction may be lifted in the future. 86 `) 87 var longRestoreHelp = i18n.G(` 88 The restore command replaces the current user, system and 89 configuration data of included snaps, with the corresponding data from 90 the specified snapshot. 91 92 By default, this command restores all the data in a snapshot. 93 Alternatively, you can specify the data of which snaps to restore, or 94 for which users, or a combination of these. 95 96 If a snap is included in a restore operation, excluding its system and 97 configuration data from the restore is not currently possible. This 98 restriction may be lifted in the future. 99 `) 100 101 type savedCmd struct { 102 clientMixin 103 durationMixin 104 ID snapshotID `long:"id"` 105 Positional struct { 106 Snaps []installedSnapName `positional-arg-name:"<snap>"` 107 } `positional-args:"yes"` 108 } 109 110 func (x *savedCmd) Execute([]string) error { 111 var setID uint64 112 var err error 113 if x.ID != "" { 114 setID, err = x.ID.ToUint() 115 if err != nil { 116 return err 117 } 118 } 119 snaps := installedSnapNames(x.Positional.Snaps) 120 list, err := x.client.SnapshotSets(setID, snaps) 121 if err != nil { 122 return err 123 } 124 if len(list) == 0 { 125 fmt.Fprintln(Stdout, i18n.G("No snapshots found.")) 126 return nil 127 } 128 w := tabWriter() 129 defer w.Flush() 130 131 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 132 // TRANSLATORS: 'Set' as in group or bag of things 133 i18n.G("Set"), 134 "Snap", 135 // TRANSLATORS: 'Age' as in how old something is 136 i18n.G("Age"), 137 i18n.G("Version"), 138 // TRANSLATORS: 'Rev' is an abbreviation of 'Revision' 139 i18n.G("Rev"), 140 i18n.G("Size"), 141 // TRANSLATORS: 'Notes' as in 'Comments' 142 i18n.G("Notes")) 143 for _, sg := range list { 144 for _, sh := range sg.Snapshots { 145 notes := []string{} 146 if sh.Auto { 147 notes = append(notes, "auto") 148 } 149 if sh.Broken != "" { 150 notes = append(notes, "broken: "+sh.Broken) 151 } 152 note := "-" 153 if len(notes) > 0 { 154 note = strings.Join(notes, ", ") 155 } 156 size := fmtSize(sh.Size) 157 age := x.fmtDuration(sh.Time) 158 fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%s\t%s\n", sg.ID, sh.Snap, age, sh.Version, sh.Revision, size, note) 159 } 160 } 161 return nil 162 } 163 164 type saveCmd struct { 165 waitMixin 166 durationMixin 167 Users string `long:"users"` 168 Positional struct { 169 Snaps []installedSnapName `positional-arg-name:"<snap>"` 170 } `positional-args:"yes"` 171 } 172 173 func (x *saveCmd) Execute([]string) error { 174 snaps := installedSnapNames(x.Positional.Snaps) 175 users := strutil.CommaSeparatedList(x.Users) 176 setID, changeID, err := x.client.SnapshotMany(snaps, users) 177 if err != nil { 178 return err 179 } 180 if _, err := x.wait(changeID); err != nil { 181 if err == noWait { 182 return nil 183 } 184 return err 185 } 186 187 y := &savedCmd{ 188 clientMixin: x.clientMixin, 189 durationMixin: x.durationMixin, 190 ID: snapshotID(strconv.FormatUint(setID, 10)), 191 } 192 return y.Execute(nil) 193 } 194 195 type forgetCmd struct { 196 waitMixin 197 Positional struct { 198 ID snapshotID `positional-arg-name:"<id>"` 199 Snaps []installedSnapName `positional-arg-name:"<snap>"` 200 } `positional-args:"yes" required:"yes"` 201 } 202 203 func (x *forgetCmd) Execute([]string) error { 204 setID, err := x.Positional.ID.ToUint() 205 if err != nil { 206 return err 207 } 208 snaps := installedSnapNames(x.Positional.Snaps) 209 changeID, err := x.client.ForgetSnapshots(setID, snaps) 210 if err != nil { 211 return err 212 } 213 _, err = x.wait(changeID) 214 if err == noWait { 215 return nil 216 } 217 if err != nil { 218 return err 219 } 220 221 if len(snaps) > 0 { 222 // TRANSLATORS: the %s is a comma-separated list of quoted snap names 223 fmt.Fprintf(Stdout, i18n.NG("Snapshot #%s of snap %s forgotten.\n", "Snapshot #%s of snaps %s forgotten.\n", len(snaps)), x.Positional.ID, strutil.Quoted(snaps)) 224 } else { 225 fmt.Fprintf(Stdout, i18n.G("Snapshot #%s forgotten.\n"), x.Positional.ID) 226 } 227 return nil 228 } 229 230 type checkSnapshotCmd struct { 231 waitMixin 232 Users string `long:"users"` 233 Positional struct { 234 ID snapshotID `positional-arg-name:"<id>"` 235 Snaps []installedSnapName `positional-arg-name:"<snap>"` 236 } `positional-args:"yes" required:"yes"` 237 } 238 239 func (x *checkSnapshotCmd) Execute([]string) error { 240 setID, err := x.Positional.ID.ToUint() 241 if err != nil { 242 return err 243 } 244 snaps := installedSnapNames(x.Positional.Snaps) 245 users := strutil.CommaSeparatedList(x.Users) 246 changeID, err := x.client.CheckSnapshots(setID, snaps, users) 247 if err != nil { 248 return err 249 } 250 _, err = x.wait(changeID) 251 if err == noWait { 252 return nil 253 } 254 if err != nil { 255 return err 256 } 257 258 // TODO: also mention the home archives that were actually checked 259 if len(snaps) > 0 { 260 // TRANSLATORS: the %s is a comma-separated list of quoted snap names 261 fmt.Fprintf(Stdout, i18n.G("Snapshot #%s of snaps %s verified successfully.\n"), 262 x.Positional.ID, strutil.Quoted(snaps)) 263 } else { 264 fmt.Fprintf(Stdout, i18n.G("Snapshot #%s verified successfully.\n"), x.Positional.ID) 265 } 266 return nil 267 } 268 269 type restoreCmd struct { 270 waitMixin 271 Users string `long:"users"` 272 Positional struct { 273 ID snapshotID `positional-arg-name:"<id>"` 274 Snaps []installedSnapName `positional-arg-name:"<snap>"` 275 } `positional-args:"yes" required:"yes"` 276 } 277 278 func (x *restoreCmd) Execute([]string) error { 279 setID, err := x.Positional.ID.ToUint() 280 if err != nil { 281 return err 282 } 283 snaps := installedSnapNames(x.Positional.Snaps) 284 users := strutil.CommaSeparatedList(x.Users) 285 changeID, err := x.client.RestoreSnapshots(setID, snaps, users) 286 if err != nil { 287 return err 288 } 289 _, err = x.wait(changeID) 290 if err == noWait { 291 return nil 292 } 293 if err != nil { 294 return err 295 } 296 297 // TODO: also mention the home archives that were actually restored 298 if len(snaps) > 0 { 299 // TRANSLATORS: the %s is a comma-separated list of quoted snap names 300 fmt.Fprintf(Stdout, i18n.G("Restored snapshot #%s of snaps %s.\n"), 301 x.Positional.ID, strutil.Quoted(snaps)) 302 } else { 303 fmt.Fprintf(Stdout, i18n.G("Restored snapshot #%s.\n"), x.Positional.ID) 304 } 305 return nil 306 } 307 308 func init() { 309 addCommand("saved", 310 shortSavedHelp, 311 longSavedHelp, 312 func() flags.Commander { 313 return &savedCmd{} 314 }, 315 durationDescs.also(map[string]string{ 316 // TRANSLATORS: This should not start with a lowercase letter. 317 "id": i18n.G("Show only a specific snapshot."), 318 }), 319 nil) 320 321 addCommand("save", 322 shortSaveHelp, 323 longSaveHelp, 324 func() flags.Commander { 325 return &saveCmd{} 326 }, durationDescs.also(waitDescs).also(map[string]string{ 327 // TRANSLATORS: This should not start with a lowercase letter. 328 "users": i18n.G("Snapshot data of only specific users (comma-separated) (default: all users)"), 329 }), nil) 330 331 addCommand("restore", 332 shortRestoreHelp, 333 longRestoreHelp, 334 func() flags.Commander { 335 return &restoreCmd{} 336 }, waitDescs.also(map[string]string{ 337 // TRANSLATORS: This should not start with a lowercase letter. 338 "users": i18n.G("Restore data of only specific users (comma-separated) (default: all users)"), 339 }), []argDesc{ 340 { 341 name: "<snap>", 342 // TRANSLATORS: This should not start with a lowercase letter. 343 desc: i18n.G("The snap for which data will be restored"), 344 }, { 345 name: "<id>", 346 // TRANSLATORS: This should not start with a lowercase letter. 347 desc: i18n.G("Set id of snapshot to restore (see 'snap help saved')"), 348 }, 349 }) 350 351 addCommand("forget", 352 shortForgetHelp, 353 longForgetHelp, 354 func() flags.Commander { 355 return &forgetCmd{} 356 }, waitDescs, []argDesc{ 357 { 358 name: "<id>", 359 // TRANSLATORS: This should not start with a lowercase letter. 360 desc: i18n.G("Set id of snapshot to delete (see 'snap help saved')"), 361 }, { 362 name: "<snap>", 363 // TRANSLATORS: This should not start with a lowercase letter. 364 desc: i18n.G("The snap for which data will be deleted"), 365 }, 366 }) 367 368 addCommand("check-snapshot", 369 shortCheckHelp, 370 longCheckHelp, 371 func() flags.Commander { 372 return &checkSnapshotCmd{} 373 }, waitDescs.also(map[string]string{ 374 // TRANSLATORS: This should not start with a lowercase letter. 375 "users": i18n.G("Check data of only specific users (comma-separated) (default: all users)"), 376 }), []argDesc{ 377 { 378 name: "<id>", 379 // TRANSLATORS: This should not start with a lowercase letter. 380 desc: i18n.G("Set id of snapshot to verify (see 'snap help saved')"), 381 }, { 382 name: "<snap>", 383 // TRANSLATORS: This should not start with a lowercase letter. 384 desc: i18n.G("The snap for which data will be verified"), 385 }, 386 }) 387 }