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  }