github.com/zntrio/harp/v2@v2.0.9/pkg/sdk/cmdutil/bug.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package cmdutil
    19  
    20  import (
    21  	"bytes"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"path/filepath"
    26  	"regexp"
    27  	"runtime"
    28  
    29  	"github.com/zntrio/harp/v2/build/version"
    30  	"github.com/zntrio/harp/v2/pkg/sdk/log"
    31  
    32  	exec "golang.org/x/sys/execabs"
    33  )
    34  
    35  // BugReport generates a bug report body
    36  //
    37  
    38  func BugReport() string {
    39  	var buf bytes.Buffer
    40  	buf.WriteString(bugHeader)
    41  	printGoVersion(&buf)
    42  	buf.WriteString("### Does this issue reproduce with the latest release?\n\n\n")
    43  	printEnvDetails(&buf)
    44  	printAppDetails(&buf)
    45  	buf.WriteString(bugFooter)
    46  
    47  	return buf.String()
    48  }
    49  
    50  const bugHeader = `<!-- Please answer these questions before submitting your issue. Thanks! -->
    51  `
    52  
    53  const bugFooter = `### What did you do?
    54  <!--
    55  If possible, provide a recipe for reproducing the error.
    56  A complete runnable program is good.
    57  -->
    58  
    59  ### What did you expect to see?
    60  ### What did you see instead?
    61  `
    62  
    63  func printGoVersion(w io.Writer) {
    64  	fmt.Fprintf(w, "### What version of Go are you using (`go version`)?\n\n")
    65  	fmt.Fprintf(w, "<pre>\n")
    66  	fmt.Fprintf(w, "$ go version\n")
    67  	printCmdOut(w, "", "go", "version")
    68  	fmt.Fprintf(w, "</pre>\n")
    69  	fmt.Fprintf(w, "\n")
    70  }
    71  
    72  func printEnvDetails(w io.Writer) {
    73  	fmt.Fprintf(w, "### What operating system and processor architecture are you using (`go env`)?\n\n")
    74  	fmt.Fprintf(w, "<details><summary><code>go env</code> Output</summary><br><pre>\n")
    75  	fmt.Fprintf(w, "$ go env\n")
    76  	printCmdOut(w, "", "go", "env")
    77  	printGoDetails(w)
    78  	printOSDetails(w)
    79  	printCDetails(w)
    80  	fmt.Fprintf(w, "</pre></details>\n\n")
    81  }
    82  
    83  func printGoDetails(w io.Writer) {
    84  	printCmdOut(w, "GOROOT/bin/go version: ", filepath.Join(runtime.GOROOT(), "bin", "go"), "version")
    85  	printCmdOut(w, "GOROOT/bin/go tool compile -V: ", filepath.Join(runtime.GOROOT(), "bin", "go"), "tool", "compile", "-V")
    86  }
    87  
    88  func printOSDetails(w io.Writer) {
    89  	switch runtime.GOOS {
    90  	case "darwin":
    91  		printCmdOut(w, "uname -v: ", "uname", "-v")
    92  		printCmdOut(w, "", "sw_vers")
    93  	case "linux":
    94  		printCmdOut(w, "uname -sr: ", "uname", "-sr")
    95  		printCmdOut(w, "", "lsb_release", "-a")
    96  		printGlibcVersion(w)
    97  	case "openbsd", "netbsd", "freebsd", "dragonfly":
    98  		printCmdOut(w, "uname -v: ", "uname", "-v")
    99  	case "illumos", "solaris":
   100  		// Be sure to use the OS-supplied uname, in "/usr/bin":
   101  		printCmdOut(w, "uname -srv: ", "/usr/bin/uname", "-srv")
   102  		out, err := os.ReadFile("/etc/release")
   103  		if err == nil {
   104  			fmt.Fprintf(w, "/etc/release: %s\n", out)
   105  		}
   106  	}
   107  }
   108  
   109  func printAppDetails(w io.Writer) {
   110  	bi := version.NewInfo()
   111  	fmt.Fprintf(w, "### What version of Secret are you using (`harp version`)?\n\n")
   112  	fmt.Fprintf(w, "<pre>\n")
   113  	fmt.Fprintf(w, "$ harp version\n")
   114  	fmt.Fprintf(w, "%s\n", bi.String())
   115  	fmt.Fprintf(w, "</pre>\n")
   116  	fmt.Fprintf(w, "\n")
   117  }
   118  
   119  func printCDetails(w io.Writer) {
   120  	printCmdOut(w, "lldb --version: ", "lldb", "--version")
   121  	cmd := exec.Command("gdb", "--version")
   122  	out, err := cmd.Output()
   123  	if err == nil {
   124  		// There's apparently no combination of command line flags
   125  		// to get gdb to spit out its version without the license and warranty.
   126  		// Print up to the first newline.
   127  		fmt.Fprintf(w, "gdb --version: %s\n", firstLine(out))
   128  	}
   129  }
   130  
   131  // printCmdOut prints the output of running the given command.
   132  // It ignores failures; 'go bug' is best effort.
   133  func printCmdOut(w io.Writer, prefix, path string, args ...string) {
   134  	cmd := exec.Command(path, args...)
   135  	out, err := cmd.Output()
   136  	if err != nil {
   137  		return
   138  	}
   139  	fmt.Fprintf(w, "%s%s\n", prefix, bytes.TrimSpace(out))
   140  }
   141  
   142  // firstLine returns the first line of a given byte slice.
   143  func firstLine(buf []byte) []byte {
   144  	idx := bytes.IndexByte(buf, '\n')
   145  	if idx > 0 {
   146  		buf = buf[:idx]
   147  	}
   148  	return bytes.TrimSpace(buf)
   149  }
   150  
   151  // printGlibcVersion prints information about the glibc version.
   152  // It ignores failures.
   153  func printGlibcVersion(w io.Writer) {
   154  	tempdir := os.TempDir()
   155  	if tempdir == "" {
   156  		return
   157  	}
   158  	src := []byte(`int main() {}`)
   159  	srcfile := filepath.Join(tempdir, "go-bug.c")
   160  	outfile := filepath.Join(tempdir, "go-bug")
   161  	err := os.WriteFile(srcfile, src, 0o600)
   162  	if err != nil {
   163  		return
   164  	}
   165  	defer func() {
   166  		log.CheckErr("unable to remove the go-bug.c file", os.Remove(srcfile))
   167  	}()
   168  	//nolint:gosec // controlled inputs
   169  	cmd := exec.Command("gcc", "-o", outfile, srcfile)
   170  	if _, err = cmd.CombinedOutput(); err != nil {
   171  		return
   172  	}
   173  	defer func() {
   174  		log.CheckErr("unable to remove the go-bug file", os.Remove(outfile))
   175  	}()
   176  	//nolint:gosec // controlled inputs
   177  	cmd = exec.Command("ldd", outfile)
   178  	out, err := cmd.CombinedOutput()
   179  	if err != nil {
   180  		return
   181  	}
   182  	re := regexp.MustCompile(`libc\.so[^ ]* => ([^ ]+)`)
   183  	m := re.FindStringSubmatch(string(out))
   184  	if m == nil {
   185  		return
   186  	}
   187  	//nolint:gosec // controlled input
   188  	cmd = exec.Command(m[1])
   189  	out, err = cmd.Output()
   190  	if err != nil {
   191  		return
   192  	}
   193  	fmt.Fprintf(w, "%s: %s\n", m[1], firstLine(out))
   194  
   195  	// print another line (the one containing version string) in case of musl libc
   196  	if idx := bytes.IndexByte(out, '\n'); bytes.Contains(out, []byte("musl")) {
   197  		fmt.Fprintf(w, "%s\n", firstLine(out[idx+1:]))
   198  	}
   199  }