Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
snap-repair: implement basic `snap-repair {list,show}` #3777
Closed
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
39ad590
implement basic `snap-repair list`
mvo5 0ce7448
add `snap-repair list --verbose`
mvo5 d98a8e0
Merge remote-tracking branch 'upstream/master' into snap-repair-list
mvo5 f411b0c
do not use state for `snap-repair list`
mvo5 565dd24
Merge branch 'repair-run-skel-with-run' into snap-repair-list
mvo5 e1501f8
update to the new log file format
mvo5 2701fd6
Merge remote-tracking branch 'upstream/master' into snap-repair-list
mvo5 f4d9944
update code to match latest dir layout
mvo5 0472031
address review feedback
mvo5 35ba743
add snap-repair show
mvo5 0a39474
use "issuer-id" in the UI
mvo5 5683f27
improve tests
mvo5 b5008b9
Merge remote-tracking branch 'upstream/master' into snap-repair-list
mvo5 d937645
improve error messages in `snap-repair show`
mvo5 bf6b57f
show "script:" before "output:" in `snap-repair show`
mvo5
Jump to file or symbol
Failed to load files and symbols.
| @@ -0,0 +1,136 @@ | ||
| +// -*- Mode: Go; indent-tabs-mode: t -*- | ||
| + | ||
| +/* | ||
| + * Copyright (C) 2017 Canonical Ltd | ||
| + * | ||
| + * This program is free software: you can redistribute it and/or modify | ||
| + * it under the terms of the GNU General Public License version 3 as | ||
| + * published by the Free Software Foundation. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU General Public License | ||
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + * | ||
| + */ | ||
| + | ||
| +package main | ||
| + | ||
| +import ( | ||
| + "fmt" | ||
| + "io/ioutil" | ||
| + "os" | ||
| + "path/filepath" | ||
| + "strconv" | ||
| + "strings" | ||
| + "text/tabwriter" | ||
| + | ||
| + "github.com/snapcore/snapd/dirs" | ||
| +) | ||
| + | ||
| +func init() { | ||
| + const ( | ||
| + short = "Lists repairs run on this device" | ||
| + long = "" | ||
| + ) | ||
| + | ||
| + if _, err := parser.AddCommand("list", short, long, &cmdList{}); err != nil { | ||
| + panic(err) | ||
| + } | ||
| + | ||
| +} | ||
| + | ||
| +type cmdList struct{} | ||
| + | ||
| +type repairTrace struct { | ||
| + repair string | ||
| + rev string | ||
| + status string | ||
| +} | ||
| + | ||
| +func newRepairTrace(artifactName, repair, status string) repairTrace { | ||
| + return repairTrace{ | ||
| + repair: repair, | ||
| + rev: revFromFilename(artifactName), | ||
| + status: status, | ||
| + } | ||
| +} | ||
| + | ||
| +func revFromFilename(name string) string { | ||
| + var rev int | ||
| + if _, err := fmt.Sscanf(name, "r%d.", &rev); err == nil { | ||
| + return strconv.Itoa(rev) | ||
| + } | ||
| + return "?" | ||
| +} | ||
| + | ||
| +func (c *cmdList) Execute([]string) error { | ||
| + w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0) | ||
| + defer w.Flush() | ||
| + | ||
| + // FIXME: this will not currently list the repairs that are | ||
| + // skipped because of e.g. wrong architecture | ||
| + | ||
| + // directory structure is: | ||
| + // canonical/ | ||
| + // 1/ | ||
| + // r0.retry | ||
| + // r0.script | ||
| + // r1.done | ||
| + // r1.script | ||
| + // 2/ | ||
| + // r3.done | ||
| + // r3.script | ||
| + var repairTraces []repairTrace | ||
| + issuersContent, err := ioutil.ReadDir(dirs.SnapRepairRunDir) | ||
| + if os.IsNotExist(err) { | ||
| + fmt.Fprintf(Stdout, "no repairs yet\n") | ||
| + return nil | ||
| + } | ||
| + if err != nil { | ||
| + return err | ||
| + } | ||
| + for _, issuer := range issuersContent { | ||
| + if !issuer.IsDir() { | ||
| + continue | ||
| + } | ||
| + issuerName := issuer.Name() | ||
| + | ||
| + seqDir := filepath.Join(dirs.SnapRepairRunDir, issuerName) | ||
| + sequences, err := ioutil.ReadDir(seqDir) | ||
| + if err != nil { | ||
| + continue | ||
| + } | ||
| + for _, seq := range sequences { | ||
| + seqName := seq.Name() | ||
| + | ||
| + repair := fmt.Sprintf("%s-%s", issuerName, seqName) | ||
| + artifactsDir := filepath.Join(dirs.SnapRepairRunDir, issuerName, seqName) | ||
| + artifacts, err := ioutil.ReadDir(artifactsDir) | ||
| + if err != nil { | ||
| + continue | ||
| + } | ||
| + for _, artifact := range artifacts { | ||
| + artifactName := artifact.Name() | ||
| + switch { | ||
| + case strings.HasSuffix(artifactName, ".retry"): | ||
| + repairTraces = append(repairTraces, newRepairTrace(artifactName, repair, "retry")) | ||
| + case strings.HasSuffix(artifactName, ".skip"): | ||
| + repairTraces = append(repairTraces, newRepairTrace(artifactName, repair, "skip")) | ||
| + case strings.HasSuffix(artifactName, ".done"): | ||
| + repairTraces = append(repairTraces, newRepairTrace(artifactName, repair, "done")) | ||
| + } | ||
|
|
||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + fmt.Fprintf(w, "Repair\tRev\tStatus\n") | ||
| + for _, t := range repairTraces { | ||
| + fmt.Fprintf(w, "%s\t%v\t%s\n", t.repair, t.rev, t.status) | ||
| + } | ||
| + | ||
| + return nil | ||
| +} | ||
| @@ -0,0 +1,77 @@ | ||
| +// -*- Mode: Go; indent-tabs-mode: t -*- | ||
| + | ||
| +/* | ||
| + * Copyright (C) 2017 Canonical Ltd | ||
| + * | ||
| + * This program is free software: you can redistribute it and/or modify | ||
| + * it under the terms of the GNU General Public License version 3 as | ||
| + * published by the Free Software Foundation. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU General Public License | ||
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + * | ||
| + */ | ||
| + | ||
| +package main_test | ||
| + | ||
| +import ( | ||
| + "io/ioutil" | ||
| + "os" | ||
| + "path/filepath" | ||
| + | ||
| + . "gopkg.in/check.v1" | ||
| + | ||
| + repair "github.com/snapcore/snapd/cmd/snap-repair" | ||
| + "github.com/snapcore/snapd/dirs" | ||
| +) | ||
| + | ||
| +func (r *repairSuite) TestListNoRepairsYet(c *C) { | ||
| + err := repair.ParseArgs([]string{"list"}) | ||
| + c.Check(err, IsNil) | ||
| + c.Check(r.Stdout(), Equals, "no repairs yet\n") | ||
| +} | ||
| + | ||
| +func makeMockRepairState(c *C) { | ||
| + // the canonical script dir content | ||
| + basedir := filepath.Join(dirs.SnapRepairRunDir, "canonical/1") | ||
| + err := os.MkdirAll(basedir, 0700) | ||
| + c.Assert(err, IsNil) | ||
| + err = ioutil.WriteFile(filepath.Join(basedir, "r3.retry"), []byte("retry output"), 0600) | ||
| + c.Assert(err, IsNil) | ||
| + err = ioutil.WriteFile(filepath.Join(basedir, "r3.script"), []byte("#!/bin/sh\necho retry output"), 0700) | ||
| + c.Assert(err, IsNil) | ||
| + | ||
| + // my-brand | ||
| + basedir = filepath.Join(dirs.SnapRepairRunDir, "my-brand/1") | ||
| + err = os.MkdirAll(basedir, 0700) | ||
| + c.Assert(err, IsNil) | ||
| + err = ioutil.WriteFile(filepath.Join(basedir, "r1.done"), []byte("done output"), 0600) | ||
| + c.Assert(err, IsNil) | ||
| + err = ioutil.WriteFile(filepath.Join(basedir, "r1.script"), []byte("#!/bin/sh\necho done output"), 0700) | ||
| + c.Assert(err, IsNil) | ||
| + | ||
| + basedir = filepath.Join(dirs.SnapRepairRunDir, "my-brand/2") | ||
| + err = os.MkdirAll(basedir, 0700) | ||
| + c.Assert(err, IsNil) | ||
| + err = ioutil.WriteFile(filepath.Join(basedir, "r2.skip"), []byte("skip output"), 0600) | ||
| + c.Assert(err, IsNil) | ||
| + err = ioutil.WriteFile(filepath.Join(basedir, "r2.script"), []byte("#!/bin/sh\necho skip output"), 0700) | ||
| + c.Assert(err, IsNil) | ||
| +} | ||
| + | ||
| +func (r *repairSuite) TestListRepairsSimple(c *C) { | ||
| + makeMockRepairState(c) | ||
| + | ||
| + err := repair.ParseArgs([]string{"list"}) | ||
| + c.Check(err, IsNil) | ||
| + c.Check(r.Stdout(), Equals, `Repair Rev Status | ||
| +canonical-1 3 retry | ||
| +my-brand-1 1 done | ||
| +my-brand-2 2 skip | ||
| +`) | ||
| +} |
| @@ -0,0 +1,114 @@ | ||
| +// -*- Mode: Go; indent-tabs-mode: t -*- | ||
| + | ||
| +/* | ||
| + * Copyright (C) 2017 Canonical Ltd | ||
| + * | ||
| + * This program is free software: you can redistribute it and/or modify | ||
| + * it under the terms of the GNU General Public License version 3 as | ||
| + * published by the Free Software Foundation. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU General Public License | ||
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + * | ||
| + */ | ||
| + | ||
| +package main | ||
| + | ||
| +import ( | ||
| + "bufio" | ||
| + "fmt" | ||
| + "io" | ||
| + "io/ioutil" | ||
| + "os" | ||
| + "path/filepath" | ||
| + "strings" | ||
| + | ||
| + "github.com/snapcore/snapd/dirs" | ||
| +) | ||
| + | ||
| +func init() { | ||
| + const ( | ||
| + short = "Shows specific repairs run on this device" | ||
| + long = "" | ||
| + ) | ||
| + | ||
| + if _, err := parser.AddCommand("show", short, long, &cmdShow{}); err != nil { | ||
| + panic(err) | ||
| + } | ||
| + | ||
| +} | ||
| + | ||
| +type cmdShow struct { | ||
| + Positional struct { | ||
| + Repair []string `positional-arg-name:"<repair>"` | ||
| + } `positional-args:"yes"` | ||
| +} | ||
| + | ||
| +func outputIndented(w io.Writer, path string) { | ||
| + f, err := os.Open(path) | ||
| + if err != nil { | ||
| + fmt.Fprintf(w, " error: %s\n", err) | ||
| + return | ||
| + } | ||
| + defer f.Close() | ||
| + | ||
| + scanner := bufio.NewScanner(f) | ||
| + for scanner.Scan() { | ||
| + fmt.Fprintf(w, " %s\n", scanner.Text()) | ||
| + } | ||
| + if scanner.Err() != nil { | ||
| + fmt.Fprintf(w, " error: %s\n", scanner.Err()) | ||
| + } | ||
| + | ||
| +} | ||
| + | ||
| +func showRepairOutput(w io.Writer, repair string) error { | ||
| + i := strings.LastIndex(repair, "-") | ||
| + if i < 0 { | ||
| + return fmt.Errorf("cannot parse repair %q", repair) | ||
| + } | ||
| + brand := repair[:i] | ||
| + seq := repair[i+1:] | ||
| + | ||
| + basedir := filepath.Join(dirs.SnapRepairRunDir, brand, seq) | ||
| + dirents, err := ioutil.ReadDir(basedir) | ||
| + if os.IsNotExist(err) { | ||
| + return fmt.Errorf("cannot find repair %q", fmt.Sprintf("%s-%s", brand, seq)) | ||
| + } | ||
| + if err != nil { | ||
| + return fmt.Errorf("cannot read snap repair directory: %v", err) | ||
| + } | ||
| + for _, dent := range dirents { | ||
| + name := dent.Name() | ||
| + rev := revFromFilename(name) | ||
| + if strings.HasSuffix(name, ".retry") || strings.HasSuffix(name, ".done") || strings.HasSuffix(name, ".skip") { | ||
| + status := filepath.Ext(name)[1:] | ||
| + fmt.Fprintf(w, "%s %s %s\n", repair, rev, status) | ||
| + | ||
| + fmt.Fprintf(w, " script:\n") | ||
| + scriptName := filepath.Join(basedir, name[:strings.LastIndex(name, ".")]+".script") | ||
| + outputIndented(w, scriptName) | ||
| + | ||
| + fmt.Fprintf(w, " output:\n") | ||
| + outputIndented(w, filepath.Join(basedir, name)) | ||
| + } | ||
| + } | ||
| + | ||
| + return nil | ||
| +} | ||
| + | ||
| +func (c *cmdShow) Execute([]string) error { | ||
| + for _, repair := range c.Positional.Repair { | ||
| + if err := showRepairOutput(Stdout, repair); err != nil { | ||
| + return err | ||
| + } | ||
| + fmt.Fprintf(Stdout, "\n") | ||
| + } | ||
| + | ||
| + return nil | ||
| +} |
Oops, something went wrong.
should we have a state of "running" if we find an ".output" file?