diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 435a2e83..224bf91f 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -21,6 +21,13 @@ type Fallback struct { done chan struct{} } +type FallbackOption struct { + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` + URL string `proxy:"url"` + Delay int `proxy:"delay"` +} + func (f *Fallback) Name() string { return f.name } @@ -98,8 +105,8 @@ func (f *Fallback) validTest() { wg.Wait() } -func NewFallback(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*Fallback, error) { - _, err := urlToMetadata(rawURL) +func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) { + _, err := urlToMetadata(option.URL) if err != nil { return nil, err } @@ -108,6 +115,7 @@ func NewFallback(name string, proxies []C.Proxy, rawURL string, delay time.Durat return nil, errors.New("The number of proxies cannot be 0") } + delay := time.Duration(option.Delay) * time.Second warpperProxies := make([]*proxy, len(proxies)) for idx := range proxies { warpperProxies[idx] = &proxy{ @@ -117,9 +125,9 @@ func NewFallback(name string, proxies []C.Proxy, rawURL string, delay time.Durat } Fallback := &Fallback{ - name: name, + name: option.Name, proxies: warpperProxies, - rawURL: rawURL, + rawURL: option.URL, delay: delay, done: make(chan struct{}), } diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index 719335f8..ee547344 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -13,6 +13,11 @@ type Selector struct { proxies map[string]C.Proxy } +type SelectorOption struct { + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` +} + func (s *Selector) Name() string { return s.name } diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 3a46c034..55056359 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "net" - "net/url" "strconv" "github.com/Dreamacro/clash/component/simple-obfs" @@ -36,6 +35,16 @@ type ShadowSocks struct { cipher core.Cipher } +type ShadowSocksOption struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Password string `proxy:"password"` + Cipher string `proxy:"cipher"` + Obfs string `proxy:"obfs,omitempty"` + ObfsHost string `proxy:"obfs-host,omitempty"` +} + func (ss *ShadowSocks) Name() string { return ss.name } @@ -62,46 +71,30 @@ func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, return &ShadowsocksAdapter{conn: c}, err } -func NewShadowSocks(name string, ssURL string, option map[string]string) (*ShadowSocks, error) { - server, cipher, password, _ := parseURL(ssURL) +func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { + server := fmt.Sprintf("%s:%d", option.Server, option.Port) + cipher := option.Cipher + password := option.Password ciph, err := core.PickCipher(cipher, nil, password) if err != nil { return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) } - obfs := "" + obfs := option.Obfs obfsHost := "bing.com" - if value, ok := option["obfs"]; ok { - obfs = value - } - - if value, ok := option["obfs-host"]; ok { - obfsHost = value + if option.ObfsHost != "" { + obfsHost = option.ObfsHost } return &ShadowSocks{ server: server, - name: name, + name: option.Name, cipher: ciph, obfs: obfs, obfsHost: obfsHost, }, nil } -func parseURL(s string) (addr, cipher, password string, err error) { - u, err := url.Parse(s) - if err != nil { - return - } - - addr = u.Host - if u.User != nil { - cipher = u.User.Username() - password, _ = u.User.Password() - } - return -} - func serializesSocksAddr(metadata *C.Metadata) []byte { var buf [][]byte aType := uint8(metadata.AddrType) diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index c9a61494..1261f810 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -31,6 +31,12 @@ type Socks5 struct { name string } +type Socks5Option struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` +} + func (ss *Socks5) Name() string { return ss.name } @@ -82,9 +88,9 @@ func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { return nil } -func NewSocks5(name, addr string) *Socks5 { +func NewSocks5(option Socks5Option) *Socks5 { return &Socks5{ - addr: addr, - name: name, + addr: fmt.Sprintf("%s:%d", option.Server, option.Port), + name: option.Name, } } diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 751a7edf..c536e612 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -1,6 +1,7 @@ package adapters import ( + "errors" "sync" "time" @@ -16,6 +17,13 @@ type URLTest struct { done chan struct{} } +type URLTestOption struct { + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` + URL string `proxy:"url"` + Delay int `proxy:"delay"` +} + func (u *URLTest) Name() string { return u.name } @@ -83,16 +91,20 @@ func (u *URLTest) speedTest() { } } -func NewURLTest(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) { - _, err := urlToMetadata(rawURL) +func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) { + _, err := urlToMetadata(option.URL) if err != nil { return nil, err } + if len(proxies) < 1 { + return nil, errors.New("The number of proxies cannot be 0") + } + delay := time.Duration(option.Delay) * time.Second urlTest := &URLTest{ - name: name, + name: option.Name, proxies: proxies[:], - rawURL: rawURL, + rawURL: option.URL, fast: proxies[0], delay: delay, done: make(chan struct{}), diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 5b8c774e..ea8d6958 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -34,11 +34,11 @@ func DelayTest(proxy C.Proxy, url string) (t int16, err error) { ExpectContinueTimeout: 1 * time.Second, } client := http.Client{Transport: transport} - req, err := client.Get(url) + resp, err := client.Get(url) if err != nil { return } - req.Body.Close() + resp.Body.Close() t = int16(time.Since(start) / time.Millisecond) return } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 4bb0817b..075a9fc0 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -30,6 +30,16 @@ type Vmess struct { client *vmess.Client } +type VmessOption struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + UUID string `proxy:"uuid"` + AlterID int `proxy:"alterId"` + Cipher string `proxy:"cipher"` + TLS bool `proxy:"tls,omitempty"` +} + func (ss *Vmess) Name() string { return ss.name } @@ -48,21 +58,21 @@ func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er return &VmessAdapter{conn: c}, err } -func NewVmess(name string, server string, uuid string, alterID uint16, security string, option map[string]string) (*Vmess, error) { - security = strings.ToLower(security) +func NewVmess(option VmessOption) (*Vmess, error) { + security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ - UUID: uuid, - AlterID: alterID, + UUID: option.UUID, + AlterID: uint16(option.AlterID), Security: security, - TLS: option["tls"] == "true", + TLS: option.TLS, }) if err != nil { return nil, err } return &Vmess{ - name: name, - server: server, + name: option.Name, + server: fmt.Sprintf("%s:%d", option.Server, option.Port), client: client, }, nil } diff --git a/config/config.go b/config/config.go index c72ffcb2..ac78af24 100644 --- a/config/config.go +++ b/config/config.go @@ -1,22 +1,21 @@ package config import ( - "bufio" "fmt" - "net/url" + "io/ioutil" "os" - "strconv" "strings" "sync" "time" "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/common/observable" + "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" R "github.com/Dreamacro/clash/rules" log "github.com/sirupsen/logrus" - "gopkg.in/ini.v1" + yaml "gopkg.in/yaml.v2" ) var ( @@ -42,6 +41,21 @@ type ProxyConfig struct { AllowLan *bool } +// RawConfig is raw config struct +type RawConfig struct { + Port int `yaml:"port"` + SocksPort int `yaml:"socks-port"` + RedirPort int `yaml:"redir-port"` + AllowLan bool `yaml:"allow-lan"` + Mode string `yaml:"mode"` + LogLevel string `yaml:"log-level"` + ExternalController string `yaml:"external-controller"` + + Proxy []map[string]interface{} `yaml:"Proxy"` + ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` + Rule []string `yaml:"Rule"` +} + // Config is clash config manager type Config struct { general *General @@ -71,17 +85,25 @@ func (c *Config) Report() chan<- interface{} { return c.reportCh } -func (c *Config) readConfig() (*ini.File, error) { +func (c *Config) readConfig() (*RawConfig, error) { if _, err := os.Stat(C.ConfigPath); os.IsNotExist(err) { return nil, err } - return ini.LoadSources( - ini.LoadOptions{ - AllowBooleanKeys: true, - UnparseableSections: []string{"Rule"}, - }, - C.ConfigPath, - ) + data, err := ioutil.ReadFile(C.ConfigPath) + if err != nil { + return nil, err + } + // config with some default value + rawConfig := &RawConfig{ + AllowLan: false, + Mode: Rule.String(), + LogLevel: C.INFO.String(), + Rule: []string{}, + Proxy: []map[string]interface{}{}, + ProxyGroup: []map[string]interface{}{}, + } + err = yaml.Unmarshal([]byte(data), &rawConfig) + return rawConfig, err } // Parse config @@ -139,15 +161,13 @@ func (c *Config) UpdateRules() error { return c.parseRules(cfg) } -func (c *Config) parseGeneral(cfg *ini.File) error { - general := cfg.Section("General") - - port := general.Key("port").RangeInt(0, 1, 65535) - socksPort := general.Key("socks-port").RangeInt(0, 1, 65535) - redirPort := general.Key("redir-port").RangeInt(0, 1, 65535) - allowLan := general.Key("allow-lan").MustBool() - logLevelString := general.Key("log-level").MustString(C.INFO.String()) - modeString := general.Key("mode").MustString(Rule.String()) +func (c *Config) parseGeneral(cfg *RawConfig) error { + port := cfg.Port + socksPort := cfg.SocksPort + redirPort := cfg.RedirPort + allowLan := cfg.AllowLan + logLevelString := cfg.LogLevel + modeString := cfg.Mode mode, exist := ModeMapping[modeString] if !exist { @@ -168,7 +188,7 @@ func (c *Config) parseGeneral(cfg *ini.File) error { LogLevel: logLevel, } - if restAddr := general.Key("external-controller").String(); restAddr != "" { + if restAddr := cfg.ExternalController; restAddr != "" { c.event <- &Event{Type: "external-controller", Payload: restAddr} } @@ -210,129 +230,128 @@ func (c *Config) UpdateProxy(pc ProxyConfig) { } } -func (c *Config) parseProxies(cfg *ini.File) error { +func (c *Config) parseProxies(cfg *RawConfig) error { proxies := make(map[string]C.Proxy) - proxiesConfig := cfg.Section("Proxy") - groupsConfig := cfg.Section("Proxy Group") + proxiesConfig := cfg.Proxy + groupsConfig := cfg.ProxyGroup + + decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) + + proxies["DIRECT"] = adapters.NewDirect() + proxies["REJECT"] = adapters.NewReject() // parse proxy - for _, key := range proxiesConfig.Keys() { - proxy := key.Strings(",") - if len(proxy) == 0 { - continue + for idx, mapping := range proxiesConfig { + proxyType, existType := mapping["type"].(string) + proxyName, existName := mapping["name"].(string) + if !existType && existName { + return fmt.Errorf("Proxy %d missing type or name", idx) } - switch proxy[0] { - // ss, server, port, cipter, password + + if _, exist := proxies[proxyName]; exist { + return fmt.Errorf("Proxy %s is the duplicate name", proxyName) + } + var proxy C.Proxy + var err error + switch proxyType { case "ss": - if len(proxy) < 5 { - continue - } - ssURL := url.URL{ - Scheme: "ss", - User: url.UserPassword(proxy[3], proxy[4]), - Host: fmt.Sprintf("%s:%s", proxy[1], proxy[2]), - } - option := parseOptions(5, proxy...) - ss, err := adapters.NewShadowSocks(key.Name(), ssURL.String(), option) + ssOption := &adapters.ShadowSocksOption{} + err = decoder.Decode(mapping, ssOption) if err != nil { - return err + break } - proxies[key.Name()] = ss - // socks5, server, port + proxy, err = adapters.NewShadowSocks(*ssOption) case "socks5": - if len(proxy) < 3 { - continue + socksOption := &adapters.Socks5Option{} + err = decoder.Decode(mapping, socksOption) + if err != nil { + break } - addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2]) - socks5 := adapters.NewSocks5(key.Name(), addr) - proxies[key.Name()] = socks5 - // vmess, server, port, uuid, alterId, security + proxy = adapters.NewSocks5(*socksOption) case "vmess": - if len(proxy) < 6 { - continue - } - addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2]) - alterID, err := strconv.Atoi(proxy[4]) + vmessOption := &adapters.VmessOption{} + err = decoder.Decode(mapping, vmessOption) if err != nil { - return err + break } - option := parseOptions(6, proxy...) - vmess, err := adapters.NewVmess(key.Name(), addr, proxy[3], uint16(alterID), proxy[5], option) - if err != nil { - return err - } - proxies[key.Name()] = vmess + proxy, err = adapters.NewVmess(*vmessOption) + default: + return fmt.Errorf("Unsupport proxy type: %s", proxyType) } + if err != nil { + return fmt.Errorf("Proxy %s: %s", proxyName, err.Error()) + } + proxies[proxyName] = proxy } // parse proxy group - for _, key := range groupsConfig.Keys() { - rule := strings.Split(key.Value(), ",") - rule = trimArr(rule) - switch rule[0] { + for idx, mapping := range groupsConfig { + groupType, existType := mapping["type"].(string) + groupName, existName := mapping["name"].(string) + if !existType && existName { + return fmt.Errorf("ProxyGroup %d: missing type or name", idx) + } + + if _, exist := proxies[groupName]; exist { + return fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) + } + var group C.Proxy + var err error + switch groupType { case "url-test": - if len(rule) < 4 { - return fmt.Errorf("URLTest need more than 4 param") - } - proxyNames := rule[1 : len(rule)-2] - delay, _ := strconv.Atoi(rule[len(rule)-1]) - url := rule[len(rule)-2] - var ps []C.Proxy - for _, name := range proxyNames { - if p, ok := proxies[name]; ok { - ps = append(ps, p) - } + urlTestOption := &adapters.URLTestOption{} + err = decoder.Decode(mapping, urlTestOption) + if err != nil { + break } - adapter, err := adapters.NewURLTest(key.Name(), ps, url, time.Duration(delay)*time.Second) - if err != nil { - return fmt.Errorf("Config error: %s", err.Error()) + var ps []C.Proxy + for _, name := range urlTestOption.Proxies { + p, ok := proxies[name] + if !ok { + return fmt.Errorf("ProxyGroup %s: proxy or proxy group '%s' not found", groupName, name) + } + ps = append(ps, p) } - proxies[key.Name()] = adapter + group, err = adapters.NewURLTest(*urlTestOption, ps) case "select": - if len(rule) < 2 { - return fmt.Errorf("Selector need more than 2 param") + selectorOption := &adapters.SelectorOption{} + err = decoder.Decode(mapping, selectorOption) + if err != nil { + break } - proxyNames := rule[1:] selectProxy := make(map[string]C.Proxy) - for _, name := range proxyNames { + for _, name := range selectorOption.Proxies { proxy, exist := proxies[name] if !exist { - return fmt.Errorf("Proxy %s not exist", name) + return fmt.Errorf("ProxyGroup %s: proxy or proxy group '%s' not found", groupName, name) } selectProxy[name] = proxy } - selector, err := adapters.NewSelector(key.Name(), selectProxy) - if err != nil { - return fmt.Errorf("Selector create error: %s", err.Error()) - } - proxies[key.Name()] = selector + group, err = adapters.NewSelector(selectorOption.Name, selectProxy) case "fallback": - if len(rule) < 4 { - return fmt.Errorf("URLTest need more than 4 param") - } - proxyNames := rule[1 : len(rule)-2] - delay, _ := strconv.Atoi(rule[len(rule)-1]) - url := rule[len(rule)-2] - var ps []C.Proxy - for _, name := range proxyNames { - if p, ok := proxies[name]; ok { - ps = append(ps, p) - } - } - - adapter, err := adapters.NewFallback(key.Name(), ps, url, time.Duration(delay)*time.Second) + fallbackOption := &adapters.FallbackOption{} + err = decoder.Decode(mapping, fallbackOption) if err != nil { - return fmt.Errorf("Config error: %s", err.Error()) + break } - proxies[key.Name()] = adapter + var ps []C.Proxy + for _, name := range fallbackOption.Proxies { + p, ok := proxies[name] + if !ok { + return fmt.Errorf("ProxyGroup %s: proxy or proxy group '%s' not found", groupName, name) + } + ps = append(ps, p) + } + group, err = adapters.NewFallback(*fallbackOption, ps) } + if err != nil { + return fmt.Errorf("Proxy %s: %s", groupName, err.Error()) + } + proxies[groupName] = group } - // init proxy proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", proxies) - proxies["DIRECT"] = adapters.NewDirect() - proxies["REJECT"] = adapters.NewReject() // close old goroutine for _, proxy := range c.proxies { @@ -348,19 +367,13 @@ func (c *Config) parseProxies(cfg *ini.File) error { return nil } -func (c *Config) parseRules(cfg *ini.File) error { +func (c *Config) parseRules(cfg *RawConfig) error { rules := []C.Rule{} - rulesConfig := cfg.Section("Rule") + rulesConfig := cfg.Rule // parse rules - reader := bufio.NewReader(strings.NewReader(rulesConfig.Body())) - for { - line, _, err := reader.ReadLine() - if err != nil { - break - } - - rule := strings.Split(string(line), ",") + for _, line := range rulesConfig { + rule := strings.Split(line, ",") if len(rule) < 3 { continue } diff --git a/config/utils.go b/config/utils.go index b0cec245..e196394b 100644 --- a/config/utils.go +++ b/config/utils.go @@ -27,20 +27,3 @@ func or(pointers ...*int) *int { } return pointers[len(pointers)-1] } - -func parseOptions(startIdx int, params ...string) map[string]string { - mapping := make(map[string]string) - if len(params) <= startIdx { - return mapping - } - - for _, option := range params[startIdx:] { - pair := strings.SplitN(option, "=", 2) - if len(pair) != 2 { - continue - } - - mapping[strings.Trim(pair[0], " ")] = strings.Trim(pair[1], " ") - } - return mapping -} diff --git a/constant/config.go b/constant/config.go index 90df19ba..0c760919 100644 --- a/constant/config.go +++ b/constant/config.go @@ -46,6 +46,6 @@ func init() { } } - ConfigPath = path.Join(dirPath, "config.ini") + ConfigPath = path.Join(dirPath, "config.yml") MMDBPath = path.Join(dirPath, "Country.mmdb") } diff --git a/go.mod b/go.mod index 4cb65dd5..5192bf8e 100644 --- a/go.mod +++ b/go.mod @@ -13,5 +13,5 @@ require ( github.com/sirupsen/logrus v1.1.0 golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 gopkg.in/eapache/channels.v1 v1.1.0 - gopkg.in/ini.v1 v1.38.3 + gopkg.in/yaml.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 27970319..c5fdb2d5 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.1 h1:Z6Z1ZQFtIKqB3ZghASl4taaJmL7SOw+Kp github.com/Dreamacro/go-shadowsocks2 v0.1.1/go.mod h1:6Fuc8zRHwXqCV9Xaw9qNfrh6OUYpDGrlPVPW4oQ34Gs= github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0= github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE= +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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -13,21 +14,26 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= +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/sirupsen/logrus v1.1.0 h1:65VZabgUiV9ktjGM5nTq0+YurgTyX+YI2lSSfDjI+qU= github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= -gopkg.in/ini.v1 v1.38.3 h1:ourkRZgR6qjJYoec9lYhX4+nuN1tEbV34dQEQ3IRk9U= -gopkg.in/ini.v1 v1.38.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=