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 }