github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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 }