github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap-repair/trace.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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  	"bufio"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"path/filepath"
    28  	"regexp"
    29  	"strconv"
    30  	"strings"
    31  
    32  	"github.com/snapcore/snapd/dirs"
    33  )
    34  
    35  // newRepairTraces returns all repairTrace about the given "brand" and "seq"
    36  // that can be found. brand, seq can be filepath.Glob expressions.
    37  func newRepairTraces(brand, seq string) ([]*repairTrace, error) {
    38  	matches, err := filepath.Glob(filepath.Join(dirs.SnapRepairRunDir, brand, seq, "*"))
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	var repairTraces []*repairTrace
    44  	for _, match := range matches {
    45  		if trace := newRepairTraceFromPath(match); trace != nil {
    46  			repairTraces = append(repairTraces, trace)
    47  		}
    48  	}
    49  
    50  	return repairTraces, nil
    51  }
    52  
    53  // repairTrace holds information about a repair that was run.
    54  type repairTrace struct {
    55  	path string
    56  }
    57  
    58  // validRepairTraceName checks that the given name looks like a valid repair
    59  // trace
    60  var validRepairTraceName = regexp.MustCompile(`^r[0-9]+\.(done|skip|retry|running)$`)
    61  
    62  // newRepairTraceFromPath takes a repair log path like
    63  // the path /var/lib/snapd/repair/run/my-brand/1/r2.done
    64  // and contructs a repair log from that.
    65  func newRepairTraceFromPath(path string) *repairTrace {
    66  	rt := &repairTrace{path: path}
    67  	if !validRepairTraceName.MatchString(filepath.Base(path)) {
    68  		return nil
    69  	}
    70  	return rt
    71  }
    72  
    73  // Repair returns the repair human readable string in the form $brand-$id
    74  func (rt *repairTrace) Repair() string {
    75  	seq := filepath.Base(filepath.Dir(rt.path))
    76  	brand := filepath.Base(filepath.Dir(filepath.Dir(rt.path)))
    77  
    78  	return fmt.Sprintf("%s-%s", brand, seq)
    79  }
    80  
    81  // Revision returns the revision of the repair
    82  func (rt *repairTrace) Revision() string {
    83  	rev, err := revFromFilepath(rt.path)
    84  	if err != nil {
    85  		// this can never happen because we check that path starts
    86  		// with the right prefix. However handle the case just in
    87  		// case.
    88  		return "-"
    89  	}
    90  	return rev
    91  }
    92  
    93  // Summary returns the summary of the repair that was run
    94  func (rt *repairTrace) Summary() string {
    95  	f, err := os.Open(rt.path)
    96  	if err != nil {
    97  		return "-"
    98  	}
    99  	defer f.Close()
   100  
   101  	needle := "summary: "
   102  	scanner := bufio.NewScanner(f)
   103  	for scanner.Scan() {
   104  		s := scanner.Text()
   105  		if strings.HasPrefix(s, needle) {
   106  			return s[len(needle):]
   107  		}
   108  	}
   109  
   110  	return "-"
   111  }
   112  
   113  // Status returns the status of the given repair {done,skip,retry,running}
   114  func (rt *repairTrace) Status() string {
   115  	return filepath.Ext(rt.path)[1:]
   116  }
   117  
   118  func indentPrefix(level int) string {
   119  	return strings.Repeat(" ", level)
   120  }
   121  
   122  // WriteScriptIndented outputs the script that produced this repair output
   123  // to the given writer w with the indent level given by indent.
   124  func (rt *repairTrace) WriteScriptIndented(w io.Writer, indent int) error {
   125  	scriptPath := rt.path[:strings.LastIndex(rt.path, ".")] + ".script"
   126  	f, err := os.Open(scriptPath)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	defer f.Close()
   131  
   132  	scanner := bufio.NewScanner(f)
   133  	for scanner.Scan() {
   134  		fmt.Fprintf(w, "%s%s\n", indentPrefix(indent), scanner.Text())
   135  	}
   136  	if scanner.Err() != nil {
   137  		return scanner.Err()
   138  	}
   139  	return nil
   140  }
   141  
   142  // WriteOutputIndented outputs the repair output to the given writer w
   143  // with the indent level given by indent.
   144  func (rt *repairTrace) WriteOutputIndented(w io.Writer, indent int) error {
   145  	f, err := os.Open(rt.path)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	defer f.Close()
   150  
   151  	scanner := bufio.NewScanner(f)
   152  	// move forward in the log to where the actual script output starts
   153  	for scanner.Scan() {
   154  		if scanner.Text() == "output:" {
   155  			break
   156  		}
   157  	}
   158  	// write the script output to w
   159  	for scanner.Scan() {
   160  		fmt.Fprintf(w, "%s%s\n", indentPrefix(indent), scanner.Text())
   161  	}
   162  	if scanner.Err() != nil {
   163  		return scanner.Err()
   164  	}
   165  	return nil
   166  }
   167  
   168  // revFromFilepath is a helper that extracts the revision number from the
   169  // filename of the repairTrace
   170  func revFromFilepath(name string) (string, error) {
   171  	var rev int
   172  	if _, err := fmt.Sscanf(filepath.Base(name), "r%d.", &rev); err == nil {
   173  		return strconv.Itoa(rev), nil
   174  	}
   175  	return "", fmt.Errorf("cannot find revision in %q", name)
   176  }