github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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/jessevdk/go-flags" 28 29 "github.com/snapcore/snapd/client" 30 "github.com/snapcore/snapd/i18n" 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 fmt.Fprintln(Stderr, i18n.G("no changes found")) 111 return nil 112 } 113 114 sort.Sort(changesByTime(changes)) 115 116 w := tabWriter() 117 118 fmt.Fprintf(w, i18n.G("ID\tStatus\tSpawn\tReady\tSummary\n")) 119 for _, chg := range changes { 120 spawnTime := c.fmtTime(chg.SpawnTime) 121 readyTime := c.fmtTime(chg.ReadyTime) 122 if chg.ReadyTime.IsZero() { 123 readyTime = "-" 124 } 125 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", chg.ID, chg.Status, spawnTime, readyTime, chg.Summary) 126 } 127 128 w.Flush() 129 fmt.Fprintln(Stdout) 130 131 return nil 132 } 133 134 func (c *cmdTasks) Execute([]string) error { 135 chid, err := c.GetChangeID() 136 if err != nil { 137 if err == noChangeFoundOK { 138 return nil 139 } 140 return err 141 } 142 143 return c.showChange(chid) 144 } 145 146 func queryChange(cli *client.Client, chid string) (*client.Change, error) { 147 chg, err := cli.Change(chid) 148 if err != nil { 149 return nil, err 150 } 151 if err := warnMaintenance(cli); err != nil { 152 return nil, err 153 } 154 return chg, nil 155 } 156 157 func (c *cmdTasks) showChange(chid string) error { 158 chg, err := queryChange(c.client, chid) 159 if err != nil { 160 return err 161 } 162 163 w := tabWriter() 164 165 fmt.Fprintf(w, i18n.G("Status\tSpawn\tReady\tSummary\n")) 166 for _, t := range chg.Tasks { 167 spawnTime := c.fmtTime(t.SpawnTime) 168 readyTime := c.fmtTime(t.ReadyTime) 169 if t.ReadyTime.IsZero() { 170 readyTime = "-" 171 } 172 summary := t.Summary 173 if t.Status == "Doing" && t.Progress.Total > 1 { 174 summary = fmt.Sprintf("%s (%.2f%%)", summary, float64(t.Progress.Done)/float64(t.Progress.Total)*100.0) 175 } 176 fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", t.Status, spawnTime, readyTime, summary) 177 } 178 179 w.Flush() 180 181 for _, t := range chg.Tasks { 182 if len(t.Log) == 0 { 183 continue 184 } 185 fmt.Fprintln(Stdout) 186 fmt.Fprintln(Stdout, line) 187 fmt.Fprintln(Stdout, t.Summary) 188 fmt.Fprintln(Stdout) 189 for _, line := range t.Log { 190 fmt.Fprintln(Stdout, line) 191 } 192 } 193 194 fmt.Fprintln(Stdout) 195 196 return nil 197 } 198 199 const line = "......................................................................" 200 201 func warnMaintenance(cli *client.Client) error { 202 if maintErr := cli.Maintenance(); maintErr != nil { 203 msg, err := errorToCmdMessage("", maintErr, nil) 204 if err != nil { 205 return err 206 } 207 fmt.Fprintf(Stderr, "WARNING: %s\n", msg) 208 } 209 return nil 210 }