feat: remove urfave/cli

pull/2/head
Karan Sharma 2020-12-13 17:49:10 +05:30
parent 6bb9a78492
commit 40c62216ec
10 changed files with 176 additions and 173 deletions

View File

@ -2,3 +2,5 @@
_Command-line DNS client written in Golang_ _Command-line DNS client written in Golang_
# WIP

13
TODO.md
View File

@ -10,6 +10,7 @@
- [x] Add DOH support - [x] Add DOH support
- [x] Add DOT support - [x] Add DOT support
- [x] Add DNS protocol on TCP mode support. - [x] Add DNS protocol on TCP mode support.
- [ ] Error on NXDomain (Realted upstream [bug](https://github.com/miekg/dns/issues/1198))
## CLI Features ## CLI Features
- [ ] `digfile` - [ ] `digfile`
@ -18,6 +19,8 @@
- [x] JSON output - [x] JSON output
- [x] Colorized output - [x] Colorized output
- [x] Table output - [x] Table output
- [ ] Parsing options free-form
- [x] Remove urfave/cli in favour of `flag`
## CLI Grunt ## CLI Grunt
- [x] Query args - [x] Query args
@ -31,8 +34,8 @@
## Documentation ## Documentation
## Release Checklist ## Release Checklist
- [ ] Add packages to all package managers - [ ] Goreleaser
- [ ] Snap - [ ] Snap
- [ ] Homebrew - [ ] Homebrew
- [ ] Alpine Linux - [ ] ARM
- [ ] ARM support too

View File

@ -1,129 +1,76 @@
package main package main
import ( import (
"fmt"
"os" "os"
"github.com/urfave/cli/v2" "github.com/knadh/koanf"
"github.com/knadh/koanf/providers/posflag"
flag "github.com/spf13/pflag"
) )
var ( var (
// Version and date of the build. This is injected at build-time. // Version and date of the build. This is injected at build-time.
buildVersion = "unknown" buildVersion = "unknown"
buildDate = "unknown" buildDate = "unknown"
k = koanf.New(".")
) )
func main() { func main() {
var ( var (
logger = initLogger() logger = initLogger()
app = cli.NewApp()
) )
// Initialize hub. // Initialize hub.
hub := NewHub(logger, buildVersion) hub := NewHub(logger, buildVersion)
// Configure CLI app. // Configure Flags
app.Name = "doggo" // Use the POSIX compliant pflag lib instead of Go's flag lib.
app.Usage = "Command-line DNS Client" f := flag.NewFlagSet("config", flag.ContinueOnError)
app.Version = buildVersion f.Usage = func() {
fmt.Println(f.FlagUsages())
// Register command line flags. os.Exit(0)
app.Flags = []cli.Flag{
&cli.StringSliceFlag{
Name: "query",
Usage: "Domain name to query",
Destination: hub.QueryFlags.QNames,
},
&cli.StringSliceFlag{
Name: "type",
Usage: "Type of DNS record to be queried (A, AAAA, MX etc)",
Destination: hub.QueryFlags.QTypes,
},
&cli.StringSliceFlag{
Name: "nameserver",
Usage: "Address of the nameserver to send packets to",
Destination: hub.QueryFlags.Nameservers,
},
&cli.StringSliceFlag{
Name: "class",
Usage: "Network class of the DNS record to be queried (IN, CH, HS etc)",
Destination: hub.QueryFlags.QClasses,
},
&cli.BoolFlag{
Name: "udp",
Usage: "Use the DNS protocol over UDP",
Aliases: []string{"U"},
},
&cli.BoolFlag{
Name: "tcp",
Usage: "Use the DNS protocol over TCP",
Aliases: []string{"T"},
Destination: &hub.QueryFlags.UseTCP,
},
&cli.BoolFlag{
Name: "https",
Usage: "Use the DNS-over-HTTPS protocol",
Aliases: []string{"H"},
Destination: &hub.QueryFlags.IsDOH,
},
&cli.BoolFlag{
Name: "tls",
Usage: "Use the DNS-over-TLS",
Aliases: []string{"S"},
Destination: &hub.QueryFlags.IsDOT,
},
&cli.BoolFlag{
Name: "ipv6",
Aliases: []string{"6"},
Usage: "Use IPv6 only",
Destination: &hub.QueryFlags.UseIPv6,
},
&cli.BoolFlag{
Name: "ipv4",
Aliases: []string{"4"},
Usage: "Use IPv4 only",
Destination: &hub.QueryFlags.UseIPv4,
},
&cli.BoolFlag{
Name: "time",
Usage: "Display how long it took for the response to arrive",
Destination: &hub.QueryFlags.DisplayTimeTaken,
},
&cli.BoolFlag{
Name: "search",
Usage: "Use the search list provided in resolv.conf. It sets the `ndots` parameter as well unless overriden by `ndots` flag.",
Destination: &hub.QueryFlags.UseSearchList,
},
&cli.IntFlag{
Name: "ndots",
Usage: "Specify the ndots paramter",
DefaultText: "Default value is that set in `/etc/resolv.conf` or 1 if no `ndots` statement is present.",
Destination: &hub.QueryFlags.Ndots,
},
&cli.BoolFlag{
Name: "json",
Aliases: []string{"J"},
Usage: "Set the output format as JSON",
Destination: &hub.QueryFlags.ShowJSON,
},
&cli.BoolFlag{
Name: "debug",
Usage: "Enable verbose logging",
Destination: &hub.QueryFlags.Verbose,
DefaultText: "false",
},
} }
app.Before = hub.loadQueryArgs // Path to one or more config files to load into koanf along with some config params.
app.Action = func(c *cli.Context) error { f.StringSliceP("query", "q", []string{}, "Domain name to query")
if len(hub.QueryFlags.QNames.Value()) == 0 { f.StringSliceP("type", "t", []string{}, "Type of DNS record to be queried (A, AAAA, MX etc)")
cli.ShowAppHelpAndExit(c, 0) f.StringSliceP("class", "c", []string{}, "Network class of the DNS record to be queried (IN, CH, HS etc)")
} f.StringSliceP("nameservers", "n", []string{}, "Address of the nameserver to send packets to")
hub.Lookup(c)
return nil // Protocol Options
f.BoolP("udp", "U", false, "Use the DNS protocol over UDP")
f.BoolP("tcp", "T", false, "Use the DNS protocol over TCP")
f.BoolP("doh", "H", false, "Use the DNS-over-HTTPS protocol")
f.BoolP("dot", "S", false, "Use the DNS-over-TLS")
// Resolver Options
f.Bool("search", false, "Use the search list provided in resolv.conf. It sets the `ndots` parameter as well unless overriden by `ndots` flag.")
f.Int("ndots", 1, "Specify the ndots paramter")
// Output Options
f.BoolP("json", "J", false, "Set the output format as JSON")
f.Bool("time", false, "Display how long it took for the response to arrive")
f.Bool("debug", false, "Enable debug mode")
// Parse and Load Flags
f.Parse(os.Args[1:])
if err := k.Load(posflag.Provider(f, ".", k), nil); err != nil {
hub.Logger.Fatalf("error loading flags: %v", err)
fmt.Println(f.FlagUsages())
os.Exit(0)
} }
// Run the app. // Run the app.
hub.Logger.Debug("Starting doggo...") hub.Logger.Debug("Starting doggo...")
err := app.Run(os.Args)
if err != nil { // Parse Query Args
logger.Errorf("oops! we encountered an issue: %s", err) hub.loadQueryArgs()
// Start App
if len(hub.QueryFlags.QNames) == 0 {
fmt.Println(f.FlagUsages())
os.Exit(0)
} }
hub.Lookup()
} }

21
cmd/help.go 100644
View File

@ -0,0 +1,21 @@
package main
import (
"fmt"
"io"
)
// Override Help Template
var helpTmpl = `NAME:
{{.Name}}
{{ range $key, $value := . }}
<li><strong>{{ $key }}</strong>: {{ $value }}</li>
{{ end }}
`
func renderCustomHelp(w io.Writer, templ string, data interface{}) {
var helpTmplVars = map[string]string{}
helpTmplVars["Name"] = "doggo"
fmt.Fprintf(w, helpTmpl, helpTmplVars)
}

View File

@ -4,7 +4,6 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/mr-karan/doggo/pkg/resolvers" "github.com/mr-karan/doggo/pkg/resolvers"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
) )
// Hub represents the structure for all app wide functions and structs. // Hub represents the structure for all app wide functions and structs.
@ -14,26 +13,24 @@ type Hub struct {
QueryFlags QueryFlags QueryFlags QueryFlags
Questions []dns.Question Questions []dns.Question
Resolver resolvers.Resolver Resolver resolvers.Resolver
cliContext *cli.Context
} }
// QueryFlags is used store the value of CLI flags. // QueryFlags is used store the value of CLI flags.
type QueryFlags struct { type QueryFlags struct {
QNames *cli.StringSlice QNames []string `koanf:"query"`
QTypes *cli.StringSlice QTypes []string `koanf:"type"`
QClasses *cli.StringSlice QClasses []string `koanf:"class"`
Nameservers *cli.StringSlice Nameservers []string `koanf:"namserver"`
IsDOH bool IsDOH bool `koanf:"doh"`
IsDOT bool IsDOT bool `koanf:"dot"`
IsUDP bool IsUDP bool `koanf:"udp"`
UseTCP bool UseTCP bool `koanf:"tcp"`
UseIPv4 bool UseIPv4 bool `koanf:"ipv4"`
UseIPv6 bool UseIPv6 bool `koanf:"ipv6"`
DisplayTimeTaken bool DisplayTimeTaken bool `koanf:"time"`
ShowJSON bool ShowJSON bool `koanf:"json"`
Verbose bool UseSearchList bool `koanf:"search"`
UseSearchList bool Ndots int `koanf:"ndots"`
Ndots int
} }
// NewHub initializes an instance of Hub which holds app wide configuration. // NewHub initializes an instance of Hub which holds app wide configuration.
@ -43,10 +40,10 @@ func NewHub(logger *logrus.Logger, buildVersion string) *Hub {
Logger: logger, Logger: logger,
Version: buildVersion, Version: buildVersion,
QueryFlags: QueryFlags{ QueryFlags: QueryFlags{
QNames: cli.NewStringSlice(), QNames: []string{},
QTypes: cli.NewStringSlice(), QTypes: []string{},
QClasses: cli.NewStringSlice(), QClasses: []string{},
Nameservers: cli.NewStringSlice(), Nameservers: []string{},
}, },
} }
return hub return hub

View File

@ -6,11 +6,10 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/mr-karan/doggo/pkg/resolvers" "github.com/mr-karan/doggo/pkg/resolvers"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
) )
// Lookup sends the DNS queries to the server. // Lookup sends the DNS queries to the server.
func (hub *Hub) Lookup(c *cli.Context) error { func (hub *Hub) Lookup() error {
err := hub.prepareQuestions() err := hub.prepareQuestions()
if err != nil { if err != nil {
return err return err
@ -30,16 +29,17 @@ func (hub *Hub) prepareQuestions() error {
var ( var (
question dns.Question question dns.Question
) )
for _, name := range hub.QueryFlags.QNames.Value() { for _, name := range hub.QueryFlags.QNames {
var ( var (
domains []string domains []string
ndots int ndots int
) )
ndots = 1
// If `search` flag is specified then fetch the search list // If `search` flag is specified then fetch the search list
// from `resolv.conf` and set the // from `resolv.conf` and set the
if hub.QueryFlags.UseSearchList { if hub.QueryFlags.UseSearchList {
list, n, err := fetchDomainList(name, hub.cliContext.IsSet("ndots"), hub.QueryFlags.Ndots) list, n, err := fetchDomainList(name, false, hub.QueryFlags.Ndots)
if err != nil { if err != nil {
return err return err
} }
@ -55,10 +55,10 @@ func (hub *Hub) prepareQuestions() error {
}).Debug("Attmepting to resolve") }).Debug("Attmepting to resolve")
question.Name = d question.Name = d
// iterate on a list of query types. // iterate on a list of query types.
for _, q := range hub.QueryFlags.QTypes.Value() { for _, q := range hub.QueryFlags.QTypes {
question.Qtype = dns.StringToType[strings.ToUpper(q)] question.Qtype = dns.StringToType[strings.ToUpper(q)]
// iterate on a list of query classes. // iterate on a list of query classes.
for _, c := range hub.QueryFlags.QClasses.Value() { for _, c := range hub.QueryFlags.QClasses {
question.Qclass = dns.StringToClass[strings.ToUpper(c)] question.Qclass = dns.StringToClass[strings.ToUpper(c)]
// append a new question for each possible pair. // append a new question for each possible pair.
hub.Questions = append(hub.Questions, question) hub.Questions = append(hub.Questions, question)

View File

@ -1,32 +1,35 @@
package main package main
import ( import (
"os"
"strings" "strings"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
) )
func (hub *Hub) loadQueryArgs(c *cli.Context) error { func (hub *Hub) loadQueryArgs() error {
// set log level // set log level
if c.Bool("debug") { if k.Bool("debug") {
// Set logger level // Set logger level
hub.Logger.SetLevel(logrus.DebugLevel) hub.Logger.SetLevel(logrus.DebugLevel)
} else { } else {
hub.Logger.SetLevel(logrus.InfoLevel) hub.Logger.SetLevel(logrus.InfoLevel)
} }
hub.cliContext = c
err := hub.loadFreeArgs(c) err := hub.loadNamedArgs()
err = hub.loadFreeArgs()
if err != nil { if err != nil {
cli.Exit("Error parsing arguments", -1) hub.Logger.WithError(err).Error("Error parsing arguments")
hub.Logger.Exit(2)
} }
err = hub.initResolver(c) err = hub.initResolver()
if err != nil { if err != nil {
cli.Exit("Error parsing nameservers", -1) hub.Logger.WithError(err).Error("Error parsing nameservers")
hub.Logger.Exit(2)
} }
hub.loadFallbacks(c) hub.loadFallbacks()
return err return err
} }
@ -37,29 +40,44 @@ func (hub *Hub) loadQueryArgs(c *cli.Context) error {
// pattern we deduce the arguments and map it to internal query // pattern we deduce the arguments and map it to internal query
// options. In case an argument isn't able to fit in any of the existing // options. In case an argument isn't able to fit in any of the existing
// pattern it is considered to be a "query name". // pattern it is considered to be a "query name".
func (hub *Hub) loadFreeArgs(c *cli.Context) error { func (hub *Hub) loadFreeArgs() error {
for _, arg := range c.Args().Slice() { args := os.Args[1:]
for _, arg := range args {
if strings.HasPrefix(arg, "--") || strings.HasPrefix(arg, "-") {
continue
}
if strings.HasPrefix(arg, "@") { if strings.HasPrefix(arg, "@") {
hub.QueryFlags.Nameservers.Set(strings.Trim(arg, "@")) hub.QueryFlags.Nameservers = append(hub.QueryFlags.Nameservers, strings.Trim(arg, "@"))
} else if _, ok := dns.StringToType[strings.ToUpper(arg)]; ok { } else if _, ok := dns.StringToType[strings.ToUpper(arg)]; ok {
hub.QueryFlags.QTypes.Set(arg) hub.QueryFlags.QTypes = append(hub.QueryFlags.QTypes, arg)
} else if _, ok := dns.StringToClass[strings.ToUpper(arg)]; ok { } else if _, ok := dns.StringToClass[strings.ToUpper(arg)]; ok {
hub.QueryFlags.QClasses.Set(arg) hub.QueryFlags.QClasses = append(hub.QueryFlags.QClasses, arg)
} else { } else {
// if nothing matches, consider it's a query name. // if nothing matches, consider it's a query name.
hub.QueryFlags.QNames.Set(arg) hub.QueryFlags.QNames = append(hub.QueryFlags.QNames, arg)
} }
} }
return nil return nil
} }
// loadNamedArgs checks for all flags and loads their
// values inside the Hub.
func (hub *Hub) loadNamedArgs() error {
// Unmarshall flags to the struct.
err := k.Unmarshal("", &hub.QueryFlags)
if err != nil {
return err
}
return nil
}
// loadFallbacks sets fallbacks for options // loadFallbacks sets fallbacks for options
// that are not specified by the user. // that are not specified by the user.
func (hub *Hub) loadFallbacks(c *cli.Context) { func (hub *Hub) loadFallbacks() {
if len(hub.QueryFlags.QTypes.Value()) == 0 { if len(hub.QueryFlags.QTypes) == 0 {
hub.QueryFlags.QTypes.Set("A") hub.QueryFlags.QTypes = append(hub.QueryFlags.QTypes, "A")
} }
if len(hub.QueryFlags.QClasses.Value()) == 0 { if len(hub.QueryFlags.QClasses) == 0 {
hub.QueryFlags.QClasses.Set("IN") hub.QueryFlags.QClasses = append(hub.QueryFlags.QClasses, "IN")
} }
} }

View File

@ -4,22 +4,21 @@ import (
"runtime" "runtime"
"github.com/mr-karan/doggo/pkg/resolvers" "github.com/mr-karan/doggo/pkg/resolvers"
"github.com/urfave/cli/v2"
) )
// initResolver checks for various flags and initialises // initResolver checks for various flags and initialises
// the correct resolver based on the config. // the correct resolver based on the config.
func (hub *Hub) initResolver(c *cli.Context) error { func (hub *Hub) initResolver() error {
// check if DOH flag is set. // check if DOH flag is set.
if hub.QueryFlags.IsDOH { if hub.QueryFlags.IsDOH {
rslvr, err := resolvers.NewDOHResolver(hub.QueryFlags.Nameservers.Value()) rslvr, err := resolvers.NewDOHResolver(hub.QueryFlags.Nameservers)
if err != nil { if err != nil {
return err return err
} }
hub.Resolver = rslvr hub.Resolver = rslvr
return nil return nil
} }
if len(hub.QueryFlags.Nameservers.Value()) == 0 { if len(hub.QueryFlags.Nameservers) == 0 {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// TODO: Add a method for reading system default nameserver in windows. // TODO: Add a method for reading system default nameserver in windows.
} else { } else {
@ -31,7 +30,7 @@ func (hub *Hub) initResolver(c *cli.Context) error {
return nil return nil
} }
} else { } else {
rslvr, err := resolvers.NewClassicResolver(hub.QueryFlags.Nameservers.Value(), resolvers.ClassicResolverOpts{ rslvr, err := resolvers.NewClassicResolver(hub.QueryFlags.Nameservers, resolvers.ClassicResolverOpts{
UseIPv4: hub.QueryFlags.UseIPv4, UseIPv4: hub.QueryFlags.UseIPv4,
UseIPv6: hub.QueryFlags.UseIPv6, UseIPv6: hub.QueryFlags.UseIPv6,
UseTLS: hub.QueryFlags.IsDOT, UseTLS: hub.QueryFlags.IsDOT,

7
go.mod
View File

@ -4,10 +4,11 @@ go 1.15
require ( require (
github.com/fatih/color v1.10.0 github.com/fatih/color v1.10.0
github.com/mattn/go-runewidth v0.0.9 github.com/knadh/koanf v0.14.0
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/miekg/dns v1.1.35 github.com/miekg/dns v1.1.35
github.com/olekukonko/tablewriter v0.0.4 github.com/olekukonko/tablewriter v0.0.4
github.com/rodaine/table v1.0.1
github.com/sirupsen/logrus v1.7.0 github.com/sirupsen/logrus v1.7.0
github.com/urfave/cli/v2 v2.3.0 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1 // indirect
) )

37
go.sum
View File

@ -1,11 +1,19 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/knadh/koanf v0.14.0 h1:h9XeG4wEiEuxdxqv/SbY7TEK+7vzrg/dOaGB+S6+mPo=
github.com/knadh/koanf v0.14.0/go.mod h1:H5mEFsTeWizwFXHKtsITL5ipsLTuAMQoGuQpp+1JL9U=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
@ -15,24 +23,25 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= github.com/rhnvrm/simples3 v0.5.0/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -47,14 +56,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=