1package command 2 3import ( 4 "flag" 5 "fmt" 6 "net" 7 "os/exec" 8 "runtime" 9 10 "github.com/hashicorp/errwrap" 11 sockaddr "github.com/hashicorp/go-sockaddr" 12 "github.com/mitchellh/cli" 13) 14 15type TechSupportCommand struct { 16 Ui cli.Ui 17 18 // outputMode controls the type of output encoding. 19 outputMode string 20 21 // flags is a list of options belonging to this command 22 flags *flag.FlagSet 23} 24 25// Description is the long-form command help. 26func (c *TechSupportCommand) Description() string { 27 return `Print out network diagnostic information that can be used by support. 28 29` + "The `sockaddr` library relies on OS-specific commands and output which can potentially be " + 30 "brittle. The `tech-support` subcommand emits all of the platform-specific " + 31 "network details required to debug why a given `sockaddr` API call is behaving " + 32 "differently than expected. The `-output` flag controls the output format. " + 33 "The default output mode is Markdown (`md`) however a raw mode (`raw`) is " + 34 "available to obtain the original output." 35} 36 37// Help returns the full help output expected by `sockaddr -h cmd` 38func (c *TechSupportCommand) Help() string { 39 return MakeHelp(c) 40} 41 42// InitOpts is responsible for setup of this command's configuration via the 43// command line. InitOpts() does not parse the arguments (see parseOpts()). 44func (c *TechSupportCommand) InitOpts() { 45 c.flags = flag.NewFlagSet("tech-support", flag.ContinueOnError) 46 c.flags.Usage = func() { c.Ui.Output(c.Help()) } 47 c.flags.StringVar(&c.outputMode, "output", "md", `Encode the output using one of Markdown ("md") or Raw ("raw")`) 48} 49 50// Run executes this command. 51func (c *TechSupportCommand) Run(args []string) int { 52 c.InitOpts() 53 rest, err := c.parseOpts(args) 54 if err != nil { 55 if errwrap.Contains(err, "flag: help requested") { 56 return 0 57 } 58 return 1 59 } 60 if len(rest) != 0 { 61 c.Ui.Error(c.Help()) 62 return 1 63 } 64 65 ri, err := sockaddr.NewRouteInfo() 66 if err != nil { 67 c.Ui.Error(fmt.Sprintf("error loading route information: %v", err)) 68 return 1 69 } 70 71 const initNumCmds = 4 72 type cmdResult struct { 73 cmd []string 74 out string 75 } 76 output := make(map[string]cmdResult, initNumCmds) 77 ri.VisitCommands(func(name string, cmd []string) { 78 out, err := exec.Command(cmd[0], cmd[1:]...).Output() 79 if err != nil { 80 out = []byte(fmt.Sprintf("ERROR: command %q failed: %v", name, err)) 81 } 82 83 output[name] = cmdResult{ 84 cmd: cmd, 85 out: string(out), 86 } 87 }) 88 89 out := c.rowWriterOutputFactory() 90 91 for cmdName, result := range output { 92 switch c.outputMode { 93 case "md": 94 c.Ui.Output(fmt.Sprintf("## cmd: `%s`", cmdName)) 95 c.Ui.Output("") 96 c.Ui.Output(fmt.Sprintf("Command: `%#v`", result.cmd)) 97 c.Ui.Output("```") 98 c.Ui.Output(result.out) 99 c.Ui.Output("```") 100 c.Ui.Output("") 101 case "raw": 102 c.Ui.Output(fmt.Sprintf("cmd: %q: %#v", cmdName, result.cmd)) 103 c.Ui.Output("") 104 c.Ui.Output(result.out) 105 c.Ui.Output("") 106 default: 107 c.Ui.Error(fmt.Sprintf("Unsupported output type: %q", c.outputMode)) 108 return 1 109 } 110 111 out("s", "GOOS", runtime.GOOS) 112 out("s", "GOARCH", runtime.GOARCH) 113 out("s", "Compiler", runtime.Compiler) 114 out("s", "Version", runtime.Version()) 115 ifs, err := net.Interfaces() 116 if err != nil { 117 out("v", "net.Interfaces", err) 118 } else { 119 for i, intf := range ifs { 120 out("s", fmt.Sprintf("net.Interfaces[%d].Name", i), intf.Name) 121 out("s", fmt.Sprintf("net.Interfaces[%d].Flags", i), intf.Flags) 122 out("+v", fmt.Sprintf("net.Interfaces[%d].Raw", i), intf) 123 addrs, err := intf.Addrs() 124 if err != nil { 125 out("v", fmt.Sprintf("net.Interfaces[%d].Addrs", i), err) 126 } else { 127 for j, addr := range addrs { 128 out("s", fmt.Sprintf("net.Interfaces[%d].Addrs[%d]", i, j), addr) 129 } 130 } 131 } 132 } 133 } 134 135 return 0 136} 137 138// Synopsis returns a terse description used when listing sub-commands. 139func (c *TechSupportCommand) Synopsis() string { 140 return `Dumps diagnostic information about a platform's network` 141} 142 143// Usage is the one-line usage description 144func (c *TechSupportCommand) Usage() string { 145 return `sockaddr tech-support [options]` 146} 147 148// VisitAllFlags forwards the visitor function to the FlagSet 149func (c *TechSupportCommand) VisitAllFlags(fn func(*flag.Flag)) { 150 c.flags.VisitAll(fn) 151} 152 153// parseOpts is responsible for parsing the options set in InitOpts(). Returns 154// a list of non-parsed flags. 155func (c *TechSupportCommand) parseOpts(args []string) ([]string, error) { 156 if err := c.flags.Parse(args); err != nil { 157 return nil, err 158 } 159 160 switch c.outputMode { 161 case "md", "markdown": 162 c.outputMode = "md" 163 case "raw": 164 default: 165 return nil, fmt.Errorf(`Invalid output mode %q, supported output types are "md" (default) and "raw"`, c.outputMode) 166 } 167 return c.flags.Args(), nil 168} 169 170func (c *TechSupportCommand) rowWriterOutputFactory() func(valueVerb, key string, val interface{}) { 171 type _Fmt string 172 type _Verb string 173 var lineNoFmt string 174 var keyVerb _Verb 175 var fmtMap map[_Verb]_Fmt 176 switch c.outputMode { 177 case "md": 178 lineNoFmt = "%02d." 179 keyVerb = "s" 180 fmtMap = map[_Verb]_Fmt{ 181 "s": "`%s`", 182 "-s": "%s", 183 "v": "`%v`", 184 "+v": "`%#v`", 185 } 186 case "raw": 187 lineNoFmt = "%02d:" 188 keyVerb = "-s" 189 fmtMap = map[_Verb]_Fmt{ 190 "s": "%q", 191 "-s": "%s", 192 "v": "%v", 193 "+v": "%#v", 194 } 195 default: 196 panic(fmt.Sprintf("Unsupported output type: %q", c.outputMode)) 197 } 198 199 var count int 200 return func(valueVerb, key string, val interface{}) { 201 count++ 202 203 keyFmt, ok := fmtMap[keyVerb] 204 if !ok { 205 panic(fmt.Sprintf("Invalid key verb: %q", keyVerb)) 206 } 207 208 valFmt, ok := fmtMap[_Verb(valueVerb)] 209 if !ok { 210 panic(fmt.Sprintf("Invalid value verb: %q", valueVerb)) 211 } 212 213 outputModeFmt := fmt.Sprintf("%s %s:\t%s", lineNoFmt, keyFmt, valFmt) 214 c.Ui.Output(fmt.Sprintf(outputModeFmt, count, key, val)) 215 } 216} 217