github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/snap/cmd_changes.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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  	"regexp"
    25  	"sort"
    26  
    27  	"github.com/snapcore/snapd/client"
    28  	"github.com/snapcore/snapd/i18n"
    29  
    30  	"github.com/jessevdk/go-flags"
    31  )
    32  
    33  var shortChangesHelp = i18n.G("List system changes")
    34  var shortTasksHelp = i18n.G("List a change's tasks")
    35  var longChangesHelp = i18n.G(`
    36  The changes command displays a summary of system changes performed recently.
    37  `)
    38  var longTasksHelp = i18n.G(`
    39  The tasks command displays a summary of tasks associated with an individual
    40  change.
    41  `)
    42  
    43  type cmdChanges struct {
    44  	clientMixin
    45  	timeMixin
    46  	Positional struct {
    47  		Snap string `positional-arg-name:"<snap>"`
    48  	} `positional-args:"yes"`
    49  }
    50  
    51  type cmdTasks struct {
    52  	timeMixin
    53  	changeIDMixin
    54  }
    55  
    56  func init() {
    57  	addCommand("changes", shortChangesHelp, longChangesHelp,
    58  		func() flags.Commander { return &cmdChanges{} }, timeDescs, nil)
    59  	addCommand("tasks", shortTasksHelp, longTasksHelp,
    60  		func() flags.Commander { return &cmdTasks{} },
    61  		changeIDMixinOptDesc.also(timeDescs),
    62  		changeIDMixinArgDesc).alias = "change"
    63  }
    64  
    65  type changesByTime []*client.Change
    66  
    67  func (s changesByTime) Len() int           { return len(s) }
    68  func (s changesByTime) Less(i, j int) bool { return s[i].SpawnTime.Before(s[j].SpawnTime) }
    69  func (s changesByTime) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    70  
    71  var allDigits = regexp.MustCompile(`^[0-9]+$`).MatchString
    72  
    73  func queryChanges(cli *client.Client, opts *client.ChangesOptions) ([]*client.Change, error) {
    74  	chgs, err := cli.Changes(opts)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	if err := warnMaintenance(cli); err != nil {
    79  		return nil, err
    80  	}
    81  	return chgs, nil
    82  }
    83  
    84  func (c *cmdChanges) Execute(args []string) error {
    85  	if len(args) > 0 {
    86  		return ErrExtraArgs
    87  	}
    88  
    89  	if allDigits(c.Positional.Snap) {
    90  		// TRANSLATORS: the %s is the argument given by the user to 'snap changes'
    91  		return fmt.Errorf(i18n.G(`'snap changes' command expects a snap name, try 'snap tasks %s'`), c.Positional.Snap)
    92  	}
    93  
    94  	if c.Positional.Snap == "everything" {
    95  		fmt.Fprintln(Stdout, i18n.G("Yes, yes it does."))
    96  		return nil
    97  	}
    98  
    99  	opts := client.ChangesOptions{
   100  		SnapName: c.Positional.Snap,
   101  		Selector: client.ChangesAll,
   102  	}
   103  
   104  	changes, err := queryChanges(c.client, &opts)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	if len(changes) == 0 {
   110  		return fmt.Errorf(i18n.G("no changes found"))
   111  	}
   112  
   113  	sort.Sort(changesByTime(changes))
   114  
   115  	w := tabWriter()
   116  
   117  	fmt.Fprintf(w, i18n.G("ID\tStatus\tSpawn\tReady\tSummary\n"))
   118  	for _, chg := range changes {
   119  		spawnTime := c.fmtTime(chg.SpawnTime)
   120  		readyTime := c.fmtTime(chg.ReadyTime)
   121  		if chg.ReadyTime.IsZero() {
   122  			readyTime = "-"
   123  		}
   124  		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", chg.ID, chg.Status, spawnTime, readyTime, chg.Summary)
   125  	}
   126  
   127  	w.Flush()
   128  	fmt.Fprintln(Stdout)
   129  
   130  	return nil
   131  }
   132  
   133  func (c *cmdTasks) Execute([]string) error {
   134  	chid, err := c.GetChangeID()
   135  	if err != nil {
   136  		if err == noChangeFoundOK {
   137  			return nil
   138  		}
   139  		return err
   140  	}
   141  
   142  	return c.showChange(chid)
   143  }
   144  
   145  func queryChange(cli *client.Client, chid string) (*client.Change, error) {
   146  	chg, err := cli.Change(chid)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	if err := warnMaintenance(cli); err != nil {
   151  		return nil, err
   152  	}
   153  	return chg, nil
   154  }
   155  
   156  func (c *cmdTasks) showChange(chid string) error {
   157  	chg, err := queryChange(c.client, chid)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	w := tabWriter()
   163  
   164  	fmt.Fprintf(w, i18n.G("Status\tSpawn\tReady\tSummary\n"))
   165  	for _, t := range chg.Tasks {
   166  		spawnTime := c.fmtTime(t.SpawnTime)
   167  		readyTime := c.fmtTime(t.ReadyTime)
   168  		if t.ReadyTime.IsZero() {
   169  			readyTime = "-"
   170  		}
   171  		summary := t.Summary
   172  		if t.Status == "Doing" && t.Progress.Total > 1 {
   173  			summary = fmt.Sprintf("%s (%.2f%%)", summary, float64(t.Progress.Done)/float64(t.Progress.Total)*100.0)
   174  		}
   175  		fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", t.Status, spawnTime, readyTime, summary)
   176  	}
   177  
   178  	w.Flush()
   179  
   180  	for _, t := range chg.Tasks {
   181  		if len(t.Log) == 0 {
   182  			continue
   183  		}
   184  		fmt.Fprintln(Stdout)
   185  		fmt.Fprintln(Stdout, line)
   186  		fmt.Fprintln(Stdout, t.Summary)
   187  		fmt.Fprintln(Stdout)
   188  		for _, line := range t.Log {
   189  			fmt.Fprintln(Stdout, line)
   190  		}
   191  	}
   192  
   193  	fmt.Fprintln(Stdout)
   194  
   195  	return nil
   196  }
   197  
   198  const line = "......................................................................"
   199  
   200  func warnMaintenance(cli *client.Client) error {
   201  	if maintErr := cli.Maintenance(); maintErr != nil {
   202  		msg, err := errorToCmdMessage("", maintErr, nil)
   203  		if err != nil {
   204  			return err
   205  		}
   206  		fmt.Fprintf(Stderr, "WARNING: %s\n", msg)
   207  	}
   208  	return nil
   209  }