snap-repair: implement basic `snap-repair {list,show}` #3777

Closed
wants to merge 15 commits into
from
@@ -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"))
+ }
@pedronis

pedronis Sep 15, 2017

Contributor

should we have a state of "running" if we find an ".output" file?

+ }
+ }
+ }
+
+ 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.