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