diff --git a/api/.travis.yml b/api/.travis.yml new file mode 100644 index 00000000..8156c577 --- /dev/null +++ b/api/.travis.yml @@ -0,0 +1,7 @@ +language: go +go: +- 1.10.x +- 1.11.x +notifications: + slack: + secure: T84DYmc4NzjYLgsRw69ckiIh1iOXKZmuB3HeRNo68/6DOvHa7ypNrSzYIOVS1n9iZmmGRk2pnDiqSBV4kqdEuQstb+T4SiqOgb9FGd7PsT2xKPg4WRRNECogeyZhxBmYilAK6IfcFI+XuLUW/i3KLdZMFfKzDE/EVHBHGueyE3aVWruUv7pLfONlOoxK44ok+Ixa5RIiVTaGmyJ3N3fjg0Css3MeC4mmwld+3zlSadWBql+Vl/K1+M9Zu2XTfreaqfLYqA2lvPorO5d7D/ZEWtxSOnSihnrlj4U0JL9sduAmCF4JndKSVvdbo0tPvdy1ODbOFUP+HFe10q0eDt39Jn2prpLr/ATAyGPWdC0DppHIQ1QNLNsjmn+F6/FIcWnO3zbPLUbMdDp9n9xg4GD2qb7vhmepd63rCMQCG6z+3WIYYDY3cgGxKKUeG+dvD2LtrsxfiXbq+o7vocwrtyrHAGZk4WEM7ZwMIzN/71qard3eD4P1OmbJwZPOOXQius50tVjN/aK1YV7X/uh0JTtwYyiL5H07IiYCGAfSPShouJ8JQBvNmGRUJwXcWLANK+sCIF9do0KsqxIkeJzcctSl2e+DP/EVRZXmxs24nP2bAdIyG+JBCuhED3vKyfLS8mR7P/LtrZL1vrbEZWRt3s8q5vhvWbcpwxGqo25GS1NDIjM= diff --git a/api/README.md b/api/README.md new file mode 100644 index 00000000..371d5ff3 --- /dev/null +++ b/api/README.md @@ -0,0 +1,18 @@ +# Go API [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/micro/go-micro/api?status.svg)](https://godoc.org/github.com/micro/go-micro/api) [![Travis CI](https://api.travis-ci.org/micro/go-micro/api.svg?branch=master)](https://travis-ci.org/micro/go-micro/api) [![Go Report Card](https://goreportcard.com/badge/micro/go-micro/api)](https://goreportcard.com/report/github.com/micro/go-micro/api) + +Go API is a pluggable API framework driven by service discovery to help build powerful public API gateways. + +## Overview + +The Go API library provides api gateway routing capabilities. A microservice architecture decouples application logic into +separate service. An api gateway provides a single entry point to consolidate these services into a unified api. The +Go API uses routes defined in service discovery metadata to generate routing rules and serve http requests. + +Go API + +Go API is the basis for the [micro api](https://micro.mu/docs/api.html). + +## Getting Started + +See the [docs](https://micro.mu/docs/go-api.html) to learn more + diff --git a/api/api.go b/api/api.go new file mode 100644 index 00000000..75ffe001 --- /dev/null +++ b/api/api.go @@ -0,0 +1,144 @@ +package api + +import ( + "errors" + "regexp" + "strings" + + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/server" +) + +// Endpoint is a mapping between an RPC method and HTTP endpoint +type Endpoint struct { + // RPC Method e.g. Greeter.Hello + Name string + // Description e.g what's this endpoint for + Description string + // API Handler e.g rpc, proxy + Handler string + // HTTP Host e.g example.com + Host []string + // HTTP Methods e.g GET, POST + Method []string + // HTTP Path e.g /greeter. Expect POSIX regex + Path []string +} + +// Service represents an API service +type Service struct { + // Name of service + Name string + // The endpoint for this service + Endpoint *Endpoint + // Versions of this service + Services []*registry.Service +} + +func strip(s string) string { + return strings.TrimSpace(s) +} + +func slice(s string) []string { + var sl []string + + for _, p := range strings.Split(s, ",") { + if str := strip(p); len(str) > 0 { + sl = append(sl, strip(p)) + } + } + + return sl +} + +// Encode encodes an endpoint to endpoint metadata +func Encode(e *Endpoint) map[string]string { + if e == nil { + return nil + } + + return map[string]string{ + "endpoint": e.Name, + "description": e.Description, + "method": strings.Join(e.Method, ","), + "path": strings.Join(e.Path, ","), + "host": strings.Join(e.Host, ","), + "handler": e.Handler, + } +} + +// Decode decodes endpoint metadata into an endpoint +func Decode(e map[string]string) *Endpoint { + if e == nil { + return nil + } + + return &Endpoint{ + Name: e["endpoint"], + Description: e["description"], + Method: slice(e["method"]), + Path: slice(e["path"]), + Host: slice(e["host"]), + Handler: e["handler"], + } +} + +// Validate validates an endpoint to guarantee it won't blow up when being served +func Validate(e *Endpoint) error { + if e == nil { + return errors.New("endpoint is nil") + } + + if len(e.Name) == 0 { + return errors.New("name required") + } + + for _, p := range e.Path { + _, err := regexp.CompilePOSIX(p) + if err != nil { + return err + } + } + + if len(e.Handler) == 0 { + return errors.New("invalid handler") + } + + return nil +} + +/* +Design ideas + +// Gateway is an api gateway interface +type Gateway interface { + // Register a http handler + Handle(pattern string, http.Handler) + // Register a route + RegisterRoute(r Route) + // Init initialises the command line. + // It also parses further options. + Init(...Option) error + // Run the gateway + Run() error +} + +// NewGateway returns a new api gateway +func NewGateway() Gateway { + return newGateway() +} +*/ + +// WithEndpoint returns a server.HandlerOption with endpoint metadata set +// +// Usage: +// +// proto.RegisterHandler(service.Server(), new(Handler), api.WithEndpoint( +// &api.Endpoint{ +// Name: "Greeter.Hello", +// Path: []string{"/greeter"}, +// }, +// )) +func WithEndpoint(e *Endpoint) server.HandlerOption { + return server.EndpointMetadata(e.Name, Encode(e)) +} diff --git a/api/api_test.go b/api/api_test.go new file mode 100644 index 00000000..ddc94a26 --- /dev/null +++ b/api/api_test.go @@ -0,0 +1,113 @@ +package api + +import ( + "strings" + "testing" +) + +func TestEncoding(t *testing.T) { + testData := []*Endpoint{ + nil, + { + Name: "Foo.Bar", + Description: "A test endpoint", + Handler: "meta", + Host: []string{"foo.com"}, + Method: []string{"GET"}, + Path: []string{"/test"}, + }, + } + + compare := func(expect, got []string) bool { + // no data to compare, return true + if len(expect) == 0 && len(got) == 0 { + return true + } + // no data expected but got some return false + if len(expect) == 0 && len(got) > 0 { + return false + } + + // compare expected with what we got + for _, e := range expect { + var seen bool + for _, g := range got { + if e == g { + seen = true + break + } + } + if !seen { + return false + } + } + + // we're done, return true + return true + } + + for _, d := range testData { + // encode + e := Encode(d) + // decode + de := Decode(e) + + // nil endpoint returns nil + if d == nil { + if e != nil { + t.Fatalf("expected nil got %v", e) + } + if de != nil { + t.Fatalf("expected nil got %v", de) + } + + continue + } + + // check encoded map + name := e["endpoint"] + desc := e["description"] + method := strings.Split(e["method"], ",") + path := strings.Split(e["path"], ",") + host := strings.Split(e["host"], ",") + handler := e["handler"] + + if name != d.Name { + t.Fatalf("expected %v got %v", d.Name, name) + } + if desc != d.Description { + t.Fatalf("expected %v got %v", d.Description, desc) + } + if handler != d.Handler { + t.Fatalf("expected %v got %v", d.Handler, handler) + } + if ok := compare(d.Method, method); !ok { + t.Fatalf("expected %v got %v", d.Method, method) + } + if ok := compare(d.Path, path); !ok { + t.Fatalf("expected %v got %v", d.Path, path) + } + if ok := compare(d.Host, host); !ok { + t.Fatalf("expected %v got %v", d.Host, host) + } + + if de.Name != d.Name { + t.Fatalf("expected %v got %v", d.Name, de.Name) + } + if de.Description != d.Description { + t.Fatalf("expected %v got %v", d.Description, de.Description) + } + if de.Handler != d.Handler { + t.Fatalf("expected %v got %v", d.Handler, de.Handler) + } + if ok := compare(d.Method, de.Method); !ok { + t.Fatalf("expected %v got %v", d.Method, de.Method) + } + if ok := compare(d.Path, de.Path); !ok { + t.Fatalf("expected %v got %v", d.Path, de.Path) + } + if ok := compare(d.Host, de.Host); !ok { + t.Fatalf("expected %v got %v", d.Host, de.Host) + } + } +} diff --git a/api/handler/api/api.go b/api/handler/api/api.go new file mode 100644 index 00000000..d5e09def --- /dev/null +++ b/api/handler/api/api.go @@ -0,0 +1,117 @@ +// Package api provides an http-rpc handler which provides the entire http request over rpc +package api + +import ( + "net/http" + + goapi "github.com/micro/go-micro/api" + "github.com/micro/go-micro/api/handler" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/errors" + "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/util/ctx" + api "github.com/micro/micro/api/proto" +) + +type apiHandler struct { + opts handler.Options + s *goapi.Service +} + +const ( + Handler = "api" +) + +// API handler is the default handler which takes api.Request and returns api.Response +func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + request, err := requestToProto(r) + if err != nil { + er := errors.InternalServerError("go.micro.api", err.Error()) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + w.Write([]byte(er.Error())) + return + } + + var service *goapi.Service + + if a.s != nil { + // we were given the service + service = a.s + } else if a.opts.Router != nil { + // try get service from router + s, err := a.opts.Router.Route(r) + if err != nil { + er := errors.InternalServerError("go.micro.api", err.Error()) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + w.Write([]byte(er.Error())) + return + } + service = s + } else { + // we have no way of routing the request + er := errors.InternalServerError("go.micro.api", "no route found") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + w.Write([]byte(er.Error())) + return + } + + // create request and response + c := a.opts.Service.Client() + req := c.NewRequest(service.Name, service.Endpoint.Name, request) + rsp := &api.Response{} + + // create the context from headers + cx := ctx.FromRequest(r) + // create strategy + so := selector.WithStrategy(strategy(service.Services)) + + if err := c.Call(cx, req, rsp, client.WithSelectOption(so)); err != nil { + w.Header().Set("Content-Type", "application/json") + ce := errors.Parse(err.Error()) + switch ce.Code { + case 0: + w.WriteHeader(500) + default: + w.WriteHeader(int(ce.Code)) + } + w.Write([]byte(ce.Error())) + return + } else if rsp.StatusCode == 0 { + rsp.StatusCode = http.StatusOK + } + + for _, header := range rsp.GetHeader() { + for _, val := range header.Values { + w.Header().Add(header.Key, val) + } + } + + if len(w.Header().Get("Content-Type")) == 0 { + w.Header().Set("Content-Type", "application/json") + } + + w.WriteHeader(int(rsp.StatusCode)) + w.Write([]byte(rsp.Body)) +} + +func (a *apiHandler) String() string { + return "api" +} + +func NewHandler(opts ...handler.Option) handler.Handler { + options := handler.NewOptions(opts...) + return &apiHandler{ + opts: options, + } +} + +func WithService(s *goapi.Service, opts ...handler.Option) handler.Handler { + options := handler.NewOptions(opts...) + return &apiHandler{ + opts: options, + s: s, + } +} diff --git a/api/handler/api/util.go b/api/handler/api/util.go new file mode 100644 index 00000000..f2520872 --- /dev/null +++ b/api/handler/api/util.go @@ -0,0 +1,107 @@ +package api + +import ( + "fmt" + "io/ioutil" + "mime" + "net" + "net/http" + "strings" + + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/selector" + api "github.com/micro/micro/api/proto" +) + +func requestToProto(r *http.Request) (*api.Request, error) { + if err := r.ParseForm(); err != nil { + return nil, fmt.Errorf("Error parsing form: %v", err) + } + + req := &api.Request{ + Path: r.URL.Path, + Method: r.Method, + Header: make(map[string]*api.Pair), + Get: make(map[string]*api.Pair), + Post: make(map[string]*api.Pair), + Url: r.URL.String(), + } + + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + ct = "application/x-www-form-urlencoded" + r.Header.Set("Content-Type", ct) + } + + switch ct { + case "application/x-www-form-urlencoded": + // expect form vals + default: + data, _ := ioutil.ReadAll(r.Body) + req.Body = string(data) + } + + // Set X-Forwarded-For if it does not exist + if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { + if prior, ok := r.Header["X-Forwarded-For"]; ok { + ip = strings.Join(prior, ", ") + ", " + ip + } + + // Set the header + req.Header["X-Forwarded-For"] = &api.Pair{ + Key: "X-Forwarded-For", + Values: []string{ip}, + } + } + + // Host is stripped from net/http Headers so let's add it + req.Header["Host"] = &api.Pair{ + Key: "Host", + Values: []string{r.Host}, + } + + // Get data + for key, vals := range r.URL.Query() { + header, ok := req.Get[key] + if !ok { + header = &api.Pair{ + Key: key, + } + req.Get[key] = header + } + header.Values = vals + } + + // Post data + for key, vals := range r.PostForm { + header, ok := req.Post[key] + if !ok { + header = &api.Pair{ + Key: key, + } + req.Post[key] = header + } + header.Values = vals + } + + for key, vals := range r.Header { + header, ok := req.Header[key] + if !ok { + header = &api.Pair{ + Key: key, + } + req.Header[key] = header + } + header.Values = vals + } + + return req, nil +} + +// strategy is a hack for selection +func strategy(services []*registry.Service) selector.Strategy { + return func(_ []*registry.Service) selector.Next { + // ignore input to this function, use services above + return selector.Random(services) + } +} diff --git a/api/handler/api/util_test.go b/api/handler/api/util_test.go new file mode 100644 index 00000000..0a82a03d --- /dev/null +++ b/api/handler/api/util_test.go @@ -0,0 +1,46 @@ +package api + +import ( + "net/http" + "net/url" + "testing" +) + +func TestRequestToProto(t *testing.T) { + testData := []*http.Request{ + &http.Request{ + Method: "GET", + Header: http.Header{ + "Header": []string{"test"}, + }, + URL: &url.URL{ + Scheme: "http", + Host: "localhost", + Path: "/foo/bar", + RawQuery: "param1=value1", + }, + }, + } + + for _, d := range testData { + p, err := requestToProto(d) + if err != nil { + t.Fatal(err) + } + if p.Path != d.URL.Path { + t.Fatalf("Expected path %s got %s", d.URL.Path, p.Path) + } + if p.Method != d.Method { + t.Fatalf("Expected method %s got %s", d.Method, p.Method) + } + for k, v := range d.Header { + if val, ok := p.Header[k]; !ok { + t.Fatalf("Expected header %s", k) + } else { + if val.Values[0] != v[0] { + t.Fatalf("Expected val %s, got %s", val.Values[0], v[0]) + } + } + } + } +} diff --git a/api/handler/broker/broker.go b/api/handler/broker/broker.go new file mode 100644 index 00000000..a9f0800d --- /dev/null +++ b/api/handler/broker/broker.go @@ -0,0 +1,268 @@ +// Package broker provides a go-micro/broker handler +package broker + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + "strings" + "sync" + "time" + + "github.com/gorilla/websocket" + "github.com/micro/go-micro/api/handler" + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/util/log" +) + +const ( + Handler = "broker" + + pingTime = (readDeadline * 9) / 10 + readLimit = 16384 + readDeadline = 60 * time.Second + writeDeadline = 10 * time.Second +) + +type brokerHandler struct { + opts handler.Options + u websocket.Upgrader +} + +type conn struct { + b broker.Broker + cType string + topic string + queue string + exit chan bool + + sync.Mutex + ws *websocket.Conn +} + +var ( + once sync.Once + contentType = "text/plain" +) + +func checkOrigin(r *http.Request) bool { + origin := r.Header["Origin"] + if len(origin) == 0 { + return true + } + u, err := url.Parse(origin[0]) + if err != nil { + return false + } + return u.Host == r.Host +} + +func (c *conn) close() { + select { + case <-c.exit: + return + default: + close(c.exit) + } +} + +func (c *conn) readLoop() { + defer func() { + c.close() + c.ws.Close() + }() + + // set read limit/deadline + c.ws.SetReadLimit(readLimit) + c.ws.SetReadDeadline(time.Now().Add(readDeadline)) + + // set close handler + ch := c.ws.CloseHandler() + c.ws.SetCloseHandler(func(code int, text string) error { + err := ch(code, text) + c.close() + return err + }) + + // set pong handler + c.ws.SetPongHandler(func(string) error { + c.ws.SetReadDeadline(time.Now().Add(readDeadline)) + return nil + }) + + for { + _, message, err := c.ws.ReadMessage() + if err != nil { + return + } + c.b.Publish(c.topic, &broker.Message{ + Header: map[string]string{"Content-Type": c.cType}, + Body: message, + }) + } +} + +func (c *conn) write(mType int, data []byte) error { + c.Lock() + c.ws.SetWriteDeadline(time.Now().Add(writeDeadline)) + err := c.ws.WriteMessage(mType, data) + c.Unlock() + return err +} + +func (c *conn) writeLoop() { + ticker := time.NewTicker(pingTime) + + var opts []broker.SubscribeOption + + if len(c.queue) > 0 { + opts = append(opts, broker.Queue(c.queue)) + } + + subscriber, err := c.b.Subscribe(c.topic, func(p broker.Publication) error { + b, err := json.Marshal(p.Message()) + if err != nil { + return nil + } + return c.write(websocket.TextMessage, b) + }, opts...) + + defer func() { + subscriber.Unsubscribe() + ticker.Stop() + c.ws.Close() + }() + + if err != nil { + log.Log(err.Error()) + return + } + + for { + select { + case <-ticker.C: + if err := c.write(websocket.PingMessage, []byte{}); err != nil { + return + } + case <-c.exit: + return + } + } +} + +func (b *brokerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + br := b.opts.Service.Client().Options().Broker + + // Setup the broker + once.Do(func() { + br.Init() + br.Connect() + }) + + // Parse + r.ParseForm() + topic := r.Form.Get("topic") + + // Can't do anything without a topic + if len(topic) == 0 { + http.Error(w, "Topic not specified", 400) + return + } + + // Post assumed to be Publish + if r.Method == "POST" { + // Create a broker message + msg := &broker.Message{ + Header: make(map[string]string), + } + + // Set header + for k, v := range r.Header { + msg.Header[k] = strings.Join(v, ", ") + } + + // Read body + b, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + // Set body + msg.Body = b + + // Publish + br.Publish(topic, msg) + return + } + + // now back to our regularly scheduled programming + + if r.Method != "GET" { + http.Error(w, "Method not allowed", 405) + return + } + + queue := r.Form.Get("queue") + + ws, err := b.u.Upgrade(w, r, nil) + if err != nil { + log.Log(err.Error()) + return + } + + cType := r.Header.Get("Content-Type") + if len(cType) == 0 { + cType = contentType + } + + c := &conn{ + b: br, + cType: cType, + topic: topic, + queue: queue, + exit: make(chan bool), + ws: ws, + } + + go c.writeLoop() + c.readLoop() +} + +func (b *brokerHandler) String() string { + return "broker" +} + +func NewHandler(opts ...handler.Option) handler.Handler { + return &brokerHandler{ + u: websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + }, + opts: handler.NewOptions(opts...), + } +} + +func WithCors(cors map[string]bool, opts ...handler.Option) handler.Handler { + return &brokerHandler{ + u: websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + if origin := r.Header.Get("Origin"); cors[origin] { + return true + } else if len(origin) > 0 && cors["*"] { + return true + } else if checkOrigin(r) { + return true + } + return false + }, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + }, + opts: handler.NewOptions(opts...), + } +} diff --git a/api/handler/cloudevents/cloudevents.go b/api/handler/cloudevents/cloudevents.go new file mode 100644 index 00000000..9b4a6293 --- /dev/null +++ b/api/handler/cloudevents/cloudevents.go @@ -0,0 +1,94 @@ +// Package cloudevents provides a cloudevents handler publishing the event using the go-micro/client +package cloudevents + +import ( + "net/http" + "path" + "regexp" + "strings" + + "github.com/micro/go-micro/api/handler" + "github.com/micro/go-micro/util/ctx" +) + +type event struct { + options handler.Options +} + +var ( + Handler = "cloudevents" + versionRe = regexp.MustCompilePOSIX("^v[0-9]+$") +) + +func eventName(parts []string) string { + return strings.Join(parts, ".") +} + +func evRoute(ns, p string) (string, string) { + p = path.Clean(p) + p = strings.TrimPrefix(p, "/") + + if len(p) == 0 { + return ns, "event" + } + + parts := strings.Split(p, "/") + + // no path + if len(parts) == 0 { + // topic: namespace + // action: event + return strings.Trim(ns, "."), "event" + } + + // Treat /v[0-9]+ as versioning + // /v1/foo/bar => topic: v1.foo action: bar + if len(parts) >= 2 && versionRe.Match([]byte(parts[0])) { + topic := ns + "." + strings.Join(parts[:2], ".") + action := eventName(parts[1:]) + return topic, action + } + + // /foo => topic: ns.foo action: foo + // /foo/bar => topic: ns.foo action: bar + topic := ns + "." + strings.Join(parts[:1], ".") + action := eventName(parts[1:]) + + return topic, action +} + +func (e *event) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // request to topic:event + // create event + // publish to topic + topic, _ := evRoute(e.options.Namespace, r.URL.Path) + + // create event + ev, err := FromRequest(r) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + // get client + c := e.options.Service.Client() + + // create publication + p := c.NewMessage(topic, ev) + + // publish event + if err := c.Publish(ctx.FromRequest(r), p); err != nil { + http.Error(w, err.Error(), 500) + return + } +} + +func (e *event) String() string { + return "cloudevents" +} + +func NewHandler(opts ...handler.Option) handler.Handler { + return &event{ + options: handler.NewOptions(opts...), + } +} diff --git a/api/handler/cloudevents/event.go b/api/handler/cloudevents/event.go new file mode 100644 index 00000000..90b4e8bb --- /dev/null +++ b/api/handler/cloudevents/event.go @@ -0,0 +1,282 @@ +/* + * From: https://github.com/serverless/event-gateway/blob/master/event/event.go + * Modified: Strip to handler requirements + * + * Copyright 2017 Serverless, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package cloudevents + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "mime" + "net/http" + "strings" + "time" + "unicode" + + "github.com/pborman/uuid" + "gopkg.in/go-playground/validator.v9" +) + +const ( + // TransformationVersion is indicative of the revision of how Event Gateway transforms a request into CloudEvents format. + TransformationVersion = "0.1" + + // CloudEventsVersion currently supported by Event Gateway + CloudEventsVersion = "0.1" +) + +// Event is a default event structure. All data that passes through the Event Gateway +// is formatted to a format defined CloudEvents v0.1 spec. +type Event struct { + EventType string `json:"eventType" validate:"required"` + EventTypeVersion string `json:"eventTypeVersion,omitempty"` + CloudEventsVersion string `json:"cloudEventsVersion" validate:"required"` + Source string `json:"source" validate:"uri,required"` + EventID string `json:"eventID" validate:"required"` + EventTime *time.Time `json:"eventTime,omitempty"` + SchemaURL string `json:"schemaURL,omitempty"` + Extensions map[string]interface{} `json:"extensions,omitempty"` + ContentType string `json:"contentType,omitempty"` + Data interface{} `json:"data"` +} + +// New return new instance of Event. +func New(eventType string, mimeType string, payload interface{}) *Event { + now := time.Now() + + event := &Event{ + EventType: eventType, + CloudEventsVersion: CloudEventsVersion, + Source: "https://micro.mu", + EventID: uuid.NewUUID().String(), + EventTime: &now, + ContentType: mimeType, + Data: payload, + Extensions: map[string]interface{}{ + "eventgateway": map[string]interface{}{ + "transformed": "true", + "transformation-version": TransformationVersion, + }, + }, + } + + event.Data = normalizePayload(event.Data, event.ContentType) + return event +} + +// FromRequest takes an HTTP request and returns an Event along with path. Most of the implementation +// is based on https://github.com/cloudevents/spec/blob/master/http-transport-binding.md. +// This function also supports legacy mode where event type is sent in Event header. +func FromRequest(r *http.Request) (*Event, error) { + contentType := r.Header.Get("Content-Type") + mimeType, _, err := mime.ParseMediaType(contentType) + if err != nil { + if err.Error() != "mime: no media type" { + return nil, err + } + mimeType = "application/octet-stream" + } + // Read request body + body := []byte{} + if r.Body != nil { + body, err = ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + } + + var event *Event + if mimeType == mimeCloudEventsJSON { // CloudEvents Structured Content Mode + return parseAsCloudEvent(mimeType, body) + } else if isCloudEventsBinaryContentMode(r.Header) { // CloudEvents Binary Content Mode + return parseAsCloudEventBinary(r.Header, body) + } else if isLegacyMode(r.Header) { + if mimeType == mimeJSON { // CloudEvent in Legacy Mode + event, err = parseAsCloudEvent(mimeType, body) + if err != nil { + return New(string(r.Header.Get("event")), mimeType, body), nil + } + return event, err + } + + return New(string(r.Header.Get("event")), mimeType, body), nil + } + + return New("http.request", mimeJSON, newHTTPRequestData(r, body)), nil +} + +// Validate Event struct +func (e *Event) Validate() error { + validate := validator.New() + err := validate.Struct(e) + if err != nil { + return fmt.Errorf("CloudEvent not valid: %v", err) + } + return nil +} + +func isLegacyMode(headers http.Header) bool { + if headers.Get("Event") != "" { + return true + } + + return false +} + +func isCloudEventsBinaryContentMode(headers http.Header) bool { + if headers.Get("CE-EventType") != "" && + headers.Get("CE-CloudEventsVersion") != "" && + headers.Get("CE-Source") != "" && + headers.Get("CE-EventID") != "" { + return true + } + + return false +} + +func parseAsCloudEventBinary(headers http.Header, payload interface{}) (*Event, error) { + event := &Event{ + EventType: headers.Get("CE-EventType"), + EventTypeVersion: headers.Get("CE-EventTypeVersion"), + CloudEventsVersion: headers.Get("CE-CloudEventsVersion"), + Source: headers.Get("CE-Source"), + EventID: headers.Get("CE-EventID"), + ContentType: headers.Get("Content-Type"), + Data: payload, + } + + err := event.Validate() + if err != nil { + return nil, err + } + + if headers.Get("CE-EventTime") != "" { + val, err := time.Parse(time.RFC3339, headers.Get("CE-EventTime")) + if err != nil { + return nil, err + } + event.EventTime = &val + } + + if val := headers.Get("CE-SchemaURL"); len(val) > 0 { + event.SchemaURL = val + } + + event.Extensions = map[string]interface{}{} + for key, val := range flatten(headers) { + if strings.HasPrefix(key, "Ce-X-") { + key = strings.TrimLeft(key, "Ce-X-") + // Make first character lowercase + runes := []rune(key) + runes[0] = unicode.ToLower(runes[0]) + event.Extensions[string(runes)] = val + } + } + + event.Data = normalizePayload(event.Data, event.ContentType) + return event, nil +} + +func flatten(h http.Header) map[string]string { + headers := map[string]string{} + for key, header := range h { + headers[key] = header[0] + if len(header) > 1 { + headers[key] = strings.Join(header, ", ") + } + } + return headers +} + +func parseAsCloudEvent(mime string, payload interface{}) (*Event, error) { + body, ok := payload.([]byte) + if ok { + event := &Event{} + err := json.Unmarshal(body, event) + if err != nil { + return nil, err + } + + err = event.Validate() + if err != nil { + return nil, err + } + + event.Data = normalizePayload(event.Data, event.ContentType) + return event, nil + } + + return nil, errors.New("couldn't cast to []byte") +} + +const ( + mimeJSON = "application/json" + mimeFormMultipart = "multipart/form-data" + mimeFormURLEncoded = "application/x-www-form-urlencoded" + mimeCloudEventsJSON = "application/cloudevents+json" +) + +// normalizePayload takes anything, checks if it's []byte array and depending on provided mime +// type converts it to either string or map[string]interface to avoid having base64 string after +// JSON marshaling. +func normalizePayload(payload interface{}, mime string) interface{} { + if bytePayload, ok := payload.([]byte); ok && len(bytePayload) > 0 { + switch { + case mime == mimeJSON || strings.HasSuffix(mime, "+json"): + var result map[string]interface{} + err := json.Unmarshal(bytePayload, &result) + if err != nil { + return payload + } + return result + case strings.HasPrefix(mime, mimeFormMultipart), mime == mimeFormURLEncoded: + return string(bytePayload) + } + } + + return payload +} + +// HTTPRequestData is a event schema used for sending events to HTTP subscriptions. +type HTTPRequestData struct { + Headers map[string]string `json:"headers"` + Query map[string][]string `json:"query"` + Body interface{} `json:"body"` + Host string `json:"host"` + Path string `json:"path"` + Method string `json:"method"` + Params map[string]string `json:"params"` +} + +// NewHTTPRequestData returns a new instance of HTTPRequestData +func newHTTPRequestData(r *http.Request, eventData interface{}) *HTTPRequestData { + req := &HTTPRequestData{ + Headers: flatten(r.Header), + Query: r.URL.Query(), + Body: eventData, + Host: r.Host, + Path: r.URL.Path, + Method: r.Method, + } + + req.Body = normalizePayload(req.Body, r.Header.Get("content-type")) + return req +} diff --git a/api/handler/event/event.go b/api/handler/event/event.go new file mode 100644 index 00000000..066043b4 --- /dev/null +++ b/api/handler/event/event.go @@ -0,0 +1,122 @@ +// Package event provides a handler which publishes an event +package event + +import ( + "fmt" + "io/ioutil" + "net/http" + "path" + "regexp" + "strings" + "time" + + "github.com/micro/go-micro/api/handler" + proto "github.com/micro/go-micro/api/proto" + "github.com/micro/go-micro/util/ctx" + "github.com/pborman/uuid" +) + +type event struct { + options handler.Options +} + +var ( + Handler = "event" + versionRe = regexp.MustCompilePOSIX("^v[0-9]+$") +) + +func eventName(parts []string) string { + return strings.Join(parts, ".") +} + +func evRoute(ns, p string) (string, string) { + p = path.Clean(p) + p = strings.TrimPrefix(p, "/") + + if len(p) == 0 { + return ns, "event" + } + + parts := strings.Split(p, "/") + + // no path + if len(parts) == 0 { + // topic: namespace + // action: event + return strings.Trim(ns, "."), "event" + } + + // Treat /v[0-9]+ as versioning + // /v1/foo/bar => topic: v1.foo action: bar + if len(parts) >= 2 && versionRe.Match([]byte(parts[0])) { + topic := ns + "." + strings.Join(parts[:2], ".") + action := eventName(parts[1:]) + return topic, action + } + + // /foo => topic: ns.foo action: foo + // /foo/bar => topic: ns.foo action: bar + topic := ns + "." + strings.Join(parts[:1], ".") + action := eventName(parts[1:]) + + return topic, action +} + +func (e *event) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // request to topic:event + // create event + // publish to topic + + topic, action := evRoute(e.options.Namespace, r.URL.Path) + + // create event + ev := &proto.Event{ + Name: action, + // TODO: dedupe event + Id: fmt.Sprintf("%s-%s-%s", topic, action, uuid.NewUUID().String()), + Header: make(map[string]*proto.Pair), + Timestamp: time.Now().Unix(), + } + + // set headers + for key, vals := range r.Header { + header, ok := ev.Header[key] + if !ok { + header = &proto.Pair{ + Key: key, + } + ev.Header[key] = header + } + header.Values = vals + } + + // set body + b, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + ev.Data = string(b) + + // get client + c := e.options.Service.Client() + + // create publication + p := c.NewMessage(topic, ev) + + // publish event + if err := c.Publish(ctx.FromRequest(r), p); err != nil { + http.Error(w, err.Error(), 500) + return + } +} + +func (e *event) String() string { + return "event" +} + +func NewHandler(opts ...handler.Option) handler.Handler { + return &event{ + options: handler.NewOptions(opts...), + } +} diff --git a/api/handler/file/file.go b/api/handler/file/file.go new file mode 100644 index 00000000..76045bef --- /dev/null +++ b/api/handler/file/file.go @@ -0,0 +1,16 @@ +// Package file serves file relative to the current directory +package file + +import ( + "net/http" +) + +type Handler struct{} + +func (h *Handler) Serve(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "."+r.URL.Path) +} + +func (h *Handler) String() string { + return "file" +} diff --git a/api/handler/handler.go b/api/handler/handler.go new file mode 100644 index 00000000..ece71b57 --- /dev/null +++ b/api/handler/handler.go @@ -0,0 +1,14 @@ +// Package handler provides http handlers +package handler + +import ( + "net/http" +) + +// Handler represents a HTTP handler that manages a request +type Handler interface { + // standard http handler + http.Handler + // name of handler + String() string +} diff --git a/api/handler/http/http.go b/api/handler/http/http.go new file mode 100644 index 00000000..cbcd2992 --- /dev/null +++ b/api/handler/http/http.go @@ -0,0 +1,100 @@ +// Package http is a http reverse proxy handler +package http + +import ( + "errors" + "fmt" + "net/http" + "net/http/httputil" + "net/url" + + "github.com/micro/go-micro/api" + "github.com/micro/go-micro/api/handler" + "github.com/micro/go-micro/selector" +) + +const ( + Handler = "http" +) + +type httpHandler struct { + options handler.Options + + // set with different initialiser + s *api.Service +} + +func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + service, err := h.getService(r) + if err != nil { + w.WriteHeader(500) + return + } + + if len(service) == 0 { + w.WriteHeader(404) + return + } + + rp, err := url.Parse(service) + if err != nil { + w.WriteHeader(500) + return + } + + httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r) +} + +// getService returns the service for this request from the selector +func (h *httpHandler) getService(r *http.Request) (string, error) { + var service *api.Service + + if h.s != nil { + // we were given the service + service = h.s + } else if h.options.Router != nil { + // try get service from router + s, err := h.options.Router.Route(r) + if err != nil { + return "", err + } + service = s + } else { + // we have no way of routing the request + return "", errors.New("no route found") + } + + // create a random selector + next := selector.Random(service.Services) + + // get the next node + s, err := next() + if err != nil { + return "", nil + } + + return fmt.Sprintf("http://%s:%d", s.Address, s.Port), nil +} + +func (h *httpHandler) String() string { + return "http" +} + +// NewHandler returns a http proxy handler +func NewHandler(opts ...handler.Option) handler.Handler { + options := handler.NewOptions(opts...) + + return &httpHandler{ + options: options, + } +} + +// WithService creates a handler with a service +func WithService(s *api.Service, opts ...handler.Option) handler.Handler { + options := handler.NewOptions(opts...) + + return &httpHandler{ + options: options, + s: s, + } +} diff --git a/api/handler/http/http_test.go b/api/handler/http/http_test.go new file mode 100644 index 00000000..37f3f2f1 --- /dev/null +++ b/api/handler/http/http_test.go @@ -0,0 +1,133 @@ +package http + +import ( + "net" + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + + "github.com/micro/go-micro/api/handler" + "github.com/micro/go-micro/api/router" + regRouter "github.com/micro/go-micro/api/router/registry" + "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/registry/memory" +) + +func testHttp(t *testing.T, path, service, ns string) { + r := memory.NewRegistry() + cmd.DefaultCmd = cmd.NewCmd(cmd.Registry(&r)) + + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer l.Close() + + parts := strings.Split(l.Addr().String(), ":") + + var host string + var port int + + host = parts[0] + port, _ = strconv.Atoi(parts[1]) + + s := ®istry.Service{ + Name: service, + Nodes: []*registry.Node{ + ®istry.Node{ + Id: service + "-1", + Address: host, + Port: port, + }, + }, + } + + r.Register(s) + defer r.Deregister(s) + + // setup the test handler + m := http.NewServeMux() + m.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`you got served`)) + }) + + // start http test serve + go http.Serve(l, m) + + // create new request and writer + w := httptest.NewRecorder() + req, err := http.NewRequest("POST", path, nil) + if err != nil { + t.Fatal(err) + } + + // initialise the handler + rt := regRouter.NewRouter( + router.WithHandler("http"), + router.WithNamespace(ns), + ) + + p := NewHandler(handler.WithRouter(rt)) + + // execute the handler + p.ServeHTTP(w, req) + + if w.Code != 200 { + t.Fatalf("Expected 200 response got %d %s", w.Code, w.Body.String()) + } + + if w.Body.String() != "you got served" { + t.Fatalf("Expected body: you got served. Got: %s", w.Body.String()) + } +} + +func TestHttpHandler(t *testing.T) { + testData := []struct { + path string + service string + namespace string + }{ + { + "/test/foo", + "go.micro.api.test", + "go.micro.api", + }, + { + "/test/foo/baz", + "go.micro.api.test", + "go.micro.api", + }, + { + "/v1/foo", + "go.micro.api.v1.foo", + "go.micro.api", + }, + { + "/v1/foo/bar", + "go.micro.api.v1.foo", + "go.micro.api", + }, + { + "/v2/baz", + "go.micro.api.v2.baz", + "go.micro.api", + }, + { + "/v2/baz/bar", + "go.micro.api.v2.baz", + "go.micro.api", + }, + { + "/v2/baz/bar", + "v2.baz", + "", + }, + } + + for _, d := range testData { + testHttp(t, d.path, d.service, d.namespace) + } +} diff --git a/api/handler/options.go b/api/handler/options.go new file mode 100644 index 00000000..6ce0f99a --- /dev/null +++ b/api/handler/options.go @@ -0,0 +1,55 @@ +package handler + +import ( + "github.com/micro/go-micro" + "github.com/micro/go-micro/api/router" +) + +type Options struct { + Namespace string + Router router.Router + Service micro.Service +} + +type Option func(o *Options) + +// NewOptions fills in the blanks +func NewOptions(opts ...Option) Options { + var options Options + for _, o := range opts { + o(&options) + } + + // create service if its blank + if options.Service == nil { + WithService(micro.NewService())(&options) + } + + // set namespace if blank + if len(options.Namespace) == 0 { + WithNamespace("go.micro.api")(&options) + } + + return options +} + +// WithNamespace specifies the namespace for the handler +func WithNamespace(s string) Option { + return func(o *Options) { + o.Namespace = s + } +} + +// WithRouter specifies a router to be used by the handler +func WithRouter(r router.Router) Option { + return func(o *Options) { + o.Router = r + } +} + +// WithService specifies a micro.Service +func WithService(s micro.Service) Option { + return func(o *Options) { + o.Service = s + } +} diff --git a/api/handler/registry/registry.go b/api/handler/registry/registry.go new file mode 100644 index 00000000..0a840793 --- /dev/null +++ b/api/handler/registry/registry.go @@ -0,0 +1,211 @@ +// Package registry is a go-micro/registry handler +package registry + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "strconv" + "time" + + "github.com/gorilla/websocket" + "github.com/micro/go-micro/api/handler" + "github.com/micro/go-micro/registry" +) + +const ( + Handler = "registry" + + pingTime = (readDeadline * 9) / 10 + readLimit = 16384 + readDeadline = 60 * time.Second + writeDeadline = 10 * time.Second +) + +type registryHandler struct { + opts handler.Options + reg registry.Registry +} + +func (rh *registryHandler) add(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + b, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + defer r.Body.Close() + + var opts []registry.RegisterOption + + // parse ttl + if ttl := r.Form.Get("ttl"); len(ttl) > 0 { + d, err := time.ParseDuration(ttl) + if err == nil { + opts = append(opts, registry.RegisterTTL(d)) + } + } + + var service *registry.Service + err = json.Unmarshal(b, &service) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + err = rh.reg.Register(service, opts...) + if err != nil { + http.Error(w, err.Error(), 500) + return + } +} + +func (rh *registryHandler) del(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + b, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + defer r.Body.Close() + + var service *registry.Service + err = json.Unmarshal(b, &service) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + err = rh.reg.Deregister(service) + if err != nil { + http.Error(w, err.Error(), 500) + return + } +} + +func (rh *registryHandler) get(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + service := r.Form.Get("service") + + var s []*registry.Service + var err error + + if len(service) == 0 { + // + upgrade := r.Header.Get("Upgrade") + connect := r.Header.Get("Connection") + + // watch if websockets + if upgrade == "websocket" && connect == "Upgrade" { + rw, err := rh.reg.Watch() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + watch(rw, w, r) + return + } + + // otherwise list services + s, err = rh.reg.ListServices() + } else { + s, err = rh.reg.GetService(service) + } + + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + if s == nil || (len(service) > 0 && (len(s) == 0 || len(s[0].Name) == 0)) { + http.Error(w, "Service not found", 404) + return + } + + b, err := json.Marshal(s) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Length", strconv.Itoa(len(b))) + w.Write(b) +} + +func ping(ws *websocket.Conn, exit chan bool) { + ticker := time.NewTicker(pingTime) + + for { + select { + case <-ticker.C: + ws.SetWriteDeadline(time.Now().Add(writeDeadline)) + err := ws.WriteMessage(websocket.PingMessage, []byte{}) + if err != nil { + return + } + case <-exit: + return + } + } +} + +func watch(rw registry.Watcher, w http.ResponseWriter, r *http.Request) { + upgrader := websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + } + + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + // we need an exit chan + exit := make(chan bool) + + defer func() { + close(exit) + }() + + // ping the socket + go ping(ws, exit) + + for { + // get next result + r, err := rw.Next() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + // write to client + ws.SetWriteDeadline(time.Now().Add(writeDeadline)) + if err := ws.WriteJSON(r); err != nil { + return + } + } +} + +func (rh *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + rh.get(w, r) + case "POST": + rh.add(w, r) + case "DELETE": + rh.del(w, r) + } +} + +func (rh *registryHandler) String() string { + return "registry" +} + +func NewHandler(opts ...handler.Option) handler.Handler { + options := handler.NewOptions(opts...) + + return ®istryHandler{ + opts: options, + reg: options.Service.Client().Options().Registry, + } +} diff --git a/api/handler/rpc/rpc.go b/api/handler/rpc/rpc.go new file mode 100644 index 00000000..28c67245 --- /dev/null +++ b/api/handler/rpc/rpc.go @@ -0,0 +1,307 @@ +// Package rpc is a go-micro rpc handler. +package rpc + +import ( + "encoding/json" + "io" + "io/ioutil" + "net/http" + "strconv" + "strings" + + "github.com/joncalhoun/qson" + "github.com/micro/go-micro/api" + "github.com/micro/go-micro/api/handler" + proto "github.com/micro/go-micro/api/internal/proto" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/codec/jsonrpc" + "github.com/micro/go-micro/codec/protorpc" + "github.com/micro/go-micro/errors" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/util/ctx" +) + +const ( + Handler = "rpc" +) + +var ( + // supported json codecs + jsonCodecs = []string{ + "application/grpc+json", + "application/json", + "application/json-rpc", + } + + // support proto codecs + protoCodecs = []string{ + "application/grpc", + "application/grpc+proto", + "application/proto", + "application/protobuf", + "application/proto-rpc", + "application/octet-stream", + } +) + +type rpcHandler struct { + opts handler.Options + s *api.Service +} + +type buffer struct { + io.ReadCloser +} + +func (b *buffer) Write(_ []byte) (int, error) { + return 0, nil +} + +// strategy is a hack for selection +func strategy(services []*registry.Service) selector.Strategy { + return func(_ []*registry.Service) selector.Next { + // ignore input to this function, use services above + return selector.Random(services) + } +} + +func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + var service *api.Service + + if h.s != nil { + // we were given the service + service = h.s + } else if h.opts.Router != nil { + // try get service from router + s, err := h.opts.Router.Route(r) + if err != nil { + writeError(w, r, errors.InternalServerError("go.micro.api", err.Error())) + return + } + service = s + } else { + // we have no way of routing the request + writeError(w, r, errors.InternalServerError("go.micro.api", "no route found")) + return + } + + // only allow post when we have the router + if r.Method != "GET" && (h.opts.Router != nil && r.Method != "POST") { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + ct := r.Header.Get("Content-Type") + + // Strip charset from Content-Type (like `application/json; charset=UTF-8`) + if idx := strings.IndexRune(ct, ';'); idx >= 0 { + ct = ct[:idx] + } + + // micro client + c := h.opts.Service.Client() + + // create strategy + so := selector.WithStrategy(strategy(service.Services)) + + // get payload + br, err := requestPayload(r) + if err != nil { + writeError(w, r, err) + return + } + + // create context + cx := ctx.FromRequest(r) + + var rsp []byte + + switch { + // json codecs + case hasCodec(ct, jsonCodecs): + var request json.RawMessage + // if the extracted payload isn't empty lets use it + if len(br) > 0 { + request = json.RawMessage(br) + } + + // create request/response + var response json.RawMessage + + req := c.NewRequest( + service.Name, + service.Endpoint.Name, + &request, + client.WithContentType(ct), + ) + + // make the call + if err := c.Call(cx, req, &response, client.WithSelectOption(so)); err != nil { + writeError(w, r, err) + return + } + + // marshall response + rsp, _ = response.MarshalJSON() + // proto codecs + case hasCodec(ct, protoCodecs): + request := &proto.Message{} + // if the extracted payload isn't empty lets use it + if len(br) > 0 { + request = proto.NewMessage(br) + } + + // create request/response + response := &proto.Message{} + + req := c.NewRequest( + service.Name, + service.Endpoint.Name, + request, + client.WithContentType(ct), + ) + + // make the call + if err := c.Call(cx, req, response, client.WithSelectOption(so)); err != nil { + writeError(w, r, err) + return + } + + // marshall response + rsp, _ = response.Marshal() + default: + http.Error(w, "Unsupported Content-Type", 400) + return + } + + // write the response + writeResponse(w, r, rsp) +} + +func (rh *rpcHandler) String() string { + return "rpc" +} + +func hasCodec(ct string, codecs []string) bool { + for _, codec := range codecs { + if ct == codec { + return true + } + } + return false +} + +// requestPayload takes a *http.Request. +// If the request is a GET the query string parameters are extracted and marshaled to JSON and the raw bytes are returned. +// If the request method is a POST the request body is read and returned +func requestPayload(r *http.Request) ([]byte, error) { + // we have to decode json-rpc and proto-rpc because we suck + // well actually because there's no proxy codec right now + switch r.Header.Get("Content-Type") { + case "application/json-rpc": + msg := codec.Message{ + Type: codec.Request, + Header: make(map[string]string), + } + c := jsonrpc.NewCodec(&buffer{r.Body}) + if err := c.ReadHeader(&msg, codec.Request); err != nil { + return nil, err + } + var raw json.RawMessage + if err := c.ReadBody(&raw); err != nil { + return nil, err + } + return ([]byte)(raw), nil + case "application/proto-rpc", "application/octet-stream": + msg := codec.Message{ + Type: codec.Request, + Header: make(map[string]string), + } + c := protorpc.NewCodec(&buffer{r.Body}) + if err := c.ReadHeader(&msg, codec.Request); err != nil { + return nil, err + } + var raw proto.Message + if err := c.ReadBody(&raw); err != nil { + return nil, err + } + b, _ := raw.Marshal() + return b, nil + } + + // otherwise as per usual + + switch r.Method { + case "GET": + if len(r.URL.RawQuery) > 0 { + return qson.ToJSON(r.URL.RawQuery) + } + case "PATCH", "POST": + return ioutil.ReadAll(r.Body) + } + + return []byte{}, nil +} + +func writeError(w http.ResponseWriter, r *http.Request, err error) { + ce := errors.Parse(err.Error()) + + switch ce.Code { + case 0: + // assuming it's totally screwed + ce.Code = 500 + ce.Id = "go.micro.api" + ce.Status = http.StatusText(500) + ce.Detail = "error during request: " + ce.Detail + w.WriteHeader(500) + default: + w.WriteHeader(int(ce.Code)) + } + + // response content type + w.Header().Set("Content-Type", "application/json") + + // Set trailers + if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { + w.Header().Set("Trailer", "grpc-status") + w.Header().Set("Trailer", "grpc-message") + w.Header().Set("grpc-status", "13") + w.Header().Set("grpc-message", ce.Detail) + } + + w.Write([]byte(ce.Error())) +} + +func writeResponse(w http.ResponseWriter, r *http.Request, rsp []byte) { + w.Header().Set("Content-Type", r.Header.Get("Content-Type")) + w.Header().Set("Content-Length", strconv.Itoa(len(rsp))) + + // Set trailers + if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { + w.Header().Set("Trailer", "grpc-status") + w.Header().Set("Trailer", "grpc-message") + w.Header().Set("grpc-status", "0") + w.Header().Set("grpc-message", "") + } + + // write response + w.Write(rsp) +} + +func NewHandler(opts ...handler.Option) handler.Handler { + options := handler.NewOptions(opts...) + return &rpcHandler{ + opts: options, + } +} + +func WithService(s *api.Service, opts ...handler.Option) handler.Handler { + options := handler.NewOptions(opts...) + return &rpcHandler{ + opts: options, + s: s, + } +} diff --git a/api/handler/rpc/rpc_test.go b/api/handler/rpc/rpc_test.go new file mode 100644 index 00000000..2804a84c --- /dev/null +++ b/api/handler/rpc/rpc_test.go @@ -0,0 +1,95 @@ +package rpc + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/micro/go-micro/api/proto" +) + +func TestRequestPayloadFromRequest(t *testing.T) { + + // our test event so that we can validate serialising / deserializing of true protos works + protoEvent := go_api.Event{ + Name: "Test", + } + + protoBytes, err := proto.Marshal(&protoEvent) + if err != nil { + t.Fatal("Failed to marshal proto", err) + } + + jsonBytes, err := json.Marshal(protoEvent) + if err != nil { + t.Fatal("Failed to marshal proto to JSON ", err) + } + + t.Run("extracting a proto from a POST request", func(t *testing.T) { + r, err := http.NewRequest("POST", "http://localhost/my/path", bytes.NewReader(protoBytes)) + if err != nil { + t.Fatalf("Failed to created http.Request: %v", err) + } + + extByte, err := requestPayload(r) + if err != nil { + t.Fatalf("Failed to extract payload from request: %v", err) + } + if string(extByte) != string(protoBytes) { + t.Fatalf("Expected %v and %v to match", string(extByte), string(protoBytes)) + } + }) + + t.Run("extracting JSON from a POST request", func(t *testing.T) { + r, err := http.NewRequest("POST", "http://localhost/my/path", bytes.NewReader(jsonBytes)) + if err != nil { + t.Fatalf("Failed to created http.Request: %v", err) + } + + extByte, err := requestPayload(r) + if err != nil { + t.Fatalf("Failed to extract payload from request: %v", err) + } + if string(extByte) != string(jsonBytes) { + t.Fatalf("Expected %v and %v to match", string(extByte), string(jsonBytes)) + } + }) + + t.Run("extracting params from a GET request", func(t *testing.T) { + + r, err := http.NewRequest("GET", "http://localhost/my/path", nil) + if err != nil { + t.Fatalf("Failed to created http.Request: %v", err) + } + + q := r.URL.Query() + q.Add("name", "Test") + r.URL.RawQuery = q.Encode() + + extByte, err := requestPayload(r) + if err != nil { + t.Fatalf("Failed to extract payload from request: %v", err) + } + if string(extByte) != string(jsonBytes) { + t.Fatalf("Expected %v and %v to match", string(extByte), string(jsonBytes)) + } + }) + + t.Run("GET request with no params", func(t *testing.T) { + + r, err := http.NewRequest("GET", "http://localhost/my/path", nil) + if err != nil { + t.Fatalf("Failed to created http.Request: %v", err) + } + + extByte, err := requestPayload(r) + if err != nil { + t.Fatalf("Failed to extract payload from request: %v", err) + } + if string(extByte) != "" { + t.Fatalf("Expected %v and %v to match", string(extByte), "") + } + }) +} diff --git a/api/handler/udp/udp.go b/api/handler/udp/udp.go new file mode 100644 index 00000000..247aa340 --- /dev/null +++ b/api/handler/udp/udp.go @@ -0,0 +1,25 @@ +// Package udp reads and write from a udp connection +package udp + +import ( + "io" + "net" + "net/http" +) + +type Handler struct{} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + c, err := net.Dial("udp", r.Host) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + go io.Copy(c, r.Body) + // write response + io.Copy(w, c) +} + +func (h *Handler) String() string { + return "udp" +} diff --git a/api/handler/unix/unix.go b/api/handler/unix/unix.go new file mode 100644 index 00000000..070aef87 --- /dev/null +++ b/api/handler/unix/unix.go @@ -0,0 +1,30 @@ +// Package unix reads from a unix socket expecting it to be in /tmp/path +package unix + +import ( + "fmt" + "io" + "net" + "net/http" + "path/filepath" +) + +type Handler struct{} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + sock := fmt.Sprintf("%s.sock", filepath.Clean(r.URL.Path)) + path := filepath.Join("/tmp", sock) + + c, err := net.Dial("unix", path) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + go io.Copy(c, r.Body) + // write response + io.Copy(w, c) +} + +func (h *Handler) String() string { + return "unix" +} diff --git a/api/handler/web/web.go b/api/handler/web/web.go new file mode 100644 index 00000000..2a6ba18f --- /dev/null +++ b/api/handler/web/web.go @@ -0,0 +1,177 @@ +// Package web contains the web handler including websocket support +package web + +import ( + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/httputil" + "net/url" + "strings" + + "github.com/micro/go-micro/api" + "github.com/micro/go-micro/api/handler" + "github.com/micro/go-micro/selector" +) + +const ( + Handler = "web" +) + +type webHandler struct { + opts handler.Options + s *api.Service +} + +func (wh *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + service, err := wh.getService(r) + if err != nil { + w.WriteHeader(500) + return + } + + if len(service) == 0 { + w.WriteHeader(404) + return + } + + rp, err := url.Parse(service) + if err != nil { + w.WriteHeader(500) + return + } + + if isWebSocket(r) { + wh.serveWebSocket(rp.Host, w, r) + return + } + + httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r) +} + +// getService returns the service for this request from the selector +func (wh *webHandler) getService(r *http.Request) (string, error) { + var service *api.Service + + if wh.s != nil { + // we were given the service + service = wh.s + } else if wh.opts.Router != nil { + // try get service from router + s, err := wh.opts.Router.Route(r) + if err != nil { + return "", err + } + service = s + } else { + // we have no way of routing the request + return "", errors.New("no route found") + } + + // create a random selector + next := selector.Random(service.Services) + + // get the next node + s, err := next() + if err != nil { + return "", nil + } + + return fmt.Sprintf("http://%s:%d", s.Address, s.Port), nil +} + +// serveWebSocket used to serve a web socket proxied connection +func (wh *webHandler) serveWebSocket(host string, w http.ResponseWriter, r *http.Request) { + req := new(http.Request) + *req = *r + + if len(host) == 0 { + http.Error(w, "invalid host", 500) + return + } + + // set x-forward-for + if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { + if ips, ok := req.Header["X-Forwarded-For"]; ok { + clientIP = strings.Join(ips, ", ") + ", " + clientIP + } + req.Header.Set("X-Forwarded-For", clientIP) + } + + // connect to the backend host + conn, err := net.Dial("tcp", host) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + // hijack the connection + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "failed to connect", 500) + return + } + + nc, _, err := hj.Hijack() + if err != nil { + return + } + + defer nc.Close() + defer conn.Close() + + if err = req.Write(conn); err != nil { + return + } + + errCh := make(chan error, 2) + + cp := func(dst io.Writer, src io.Reader) { + _, err := io.Copy(dst, src) + errCh <- err + } + + go cp(conn, nc) + go cp(nc, conn) + + <-errCh +} + +func isWebSocket(r *http.Request) bool { + contains := func(key, val string) bool { + vv := strings.Split(r.Header.Get(key), ",") + for _, v := range vv { + if val == strings.ToLower(strings.TrimSpace(v)) { + return true + } + } + return false + } + + if contains("Connection", "upgrade") && contains("Upgrade", "websocket") { + return true + } + + return false +} + +func (wh *webHandler) String() string { + return "web" +} + +func NewHandler(opts ...handler.Option) handler.Handler { + return &webHandler{ + opts: handler.NewOptions(opts...), + } +} + +func WithService(s *api.Service, opts ...handler.Option) handler.Handler { + options := handler.NewOptions(opts...) + + return &webHandler{ + opts: options, + s: s, + } +} diff --git a/api/internal/proto/message.pb.go b/api/internal/proto/message.pb.go new file mode 100644 index 00000000..aae2743f --- /dev/null +++ b/api/internal/proto/message.pb.go @@ -0,0 +1,28 @@ +package proto + +type Message struct { + data []byte +} + +func (m *Message) ProtoMessage() {} + +func (m *Message) Reset() { + *m = Message{} +} + +func (m *Message) String() string { + return string(m.data) +} + +func (m *Message) Marshal() ([]byte, error) { + return m.data, nil +} + +func (m *Message) Unmarshal(data []byte) error { + m.data = data + return nil +} + +func NewMessage(data []byte) *Message { + return &Message{data} +} diff --git a/api/proto/api.micro.go b/api/proto/api.micro.go new file mode 100644 index 00000000..b7b01f8b --- /dev/null +++ b/api/proto/api.micro.go @@ -0,0 +1,31 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: github.com/micro/go-micro/api/proto/api.proto + +/* +Package go_api is a generated protocol buffer package. + +It is generated from these files: + github.com/micro/go-micro/api/proto/api.proto + +It has these top-level messages: + Pair + Request + Response + Event +*/ +package go_api + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package diff --git a/api/proto/api.pb.go b/api/proto/api.pb.go new file mode 100644 index 00000000..3304d4e1 --- /dev/null +++ b/api/proto/api.pb.go @@ -0,0 +1,332 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: github.com/micro/go-micro/api/proto/api.proto + +package go_api + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Pair struct { + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Values []string `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Pair) Reset() { *m = Pair{} } +func (m *Pair) String() string { return proto.CompactTextString(m) } +func (*Pair) ProtoMessage() {} +func (*Pair) Descriptor() ([]byte, []int) { + return fileDescriptor_api_17a7876430d97ebd, []int{0} +} +func (m *Pair) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Pair.Unmarshal(m, b) +} +func (m *Pair) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Pair.Marshal(b, m, deterministic) +} +func (dst *Pair) XXX_Merge(src proto.Message) { + xxx_messageInfo_Pair.Merge(dst, src) +} +func (m *Pair) XXX_Size() int { + return xxx_messageInfo_Pair.Size(m) +} +func (m *Pair) XXX_DiscardUnknown() { + xxx_messageInfo_Pair.DiscardUnknown(m) +} + +var xxx_messageInfo_Pair proto.InternalMessageInfo + +func (m *Pair) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +func (m *Pair) GetValues() []string { + if m != nil { + return m.Values + } + return nil +} + +// A HTTP request as RPC +// Forward by the api handler +type Request struct { + Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Header map[string]*Pair `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Get map[string]*Pair `protobuf:"bytes,4,rep,name=get,proto3" json:"get,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Post map[string]*Pair `protobuf:"bytes,5,rep,name=post,proto3" json:"post,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` + Url string `protobuf:"bytes,7,opt,name=url,proto3" json:"url,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Request) Reset() { *m = Request{} } +func (m *Request) String() string { return proto.CompactTextString(m) } +func (*Request) ProtoMessage() {} +func (*Request) Descriptor() ([]byte, []int) { + return fileDescriptor_api_17a7876430d97ebd, []int{1} +} +func (m *Request) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Request.Unmarshal(m, b) +} +func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Request.Marshal(b, m, deterministic) +} +func (dst *Request) XXX_Merge(src proto.Message) { + xxx_messageInfo_Request.Merge(dst, src) +} +func (m *Request) XXX_Size() int { + return xxx_messageInfo_Request.Size(m) +} +func (m *Request) XXX_DiscardUnknown() { + xxx_messageInfo_Request.DiscardUnknown(m) +} + +var xxx_messageInfo_Request proto.InternalMessageInfo + +func (m *Request) GetMethod() string { + if m != nil { + return m.Method + } + return "" +} + +func (m *Request) GetPath() string { + if m != nil { + return m.Path + } + return "" +} + +func (m *Request) GetHeader() map[string]*Pair { + if m != nil { + return m.Header + } + return nil +} + +func (m *Request) GetGet() map[string]*Pair { + if m != nil { + return m.Get + } + return nil +} + +func (m *Request) GetPost() map[string]*Pair { + if m != nil { + return m.Post + } + return nil +} + +func (m *Request) GetBody() string { + if m != nil { + return m.Body + } + return "" +} + +func (m *Request) GetUrl() string { + if m != nil { + return m.Url + } + return "" +} + +// A HTTP response as RPC +// Expected response for the api handler +type Response struct { + StatusCode int32 `protobuf:"varint,1,opt,name=statusCode,proto3" json:"statusCode,omitempty"` + Header map[string]*Pair `protobuf:"bytes,2,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Body string `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} +func (*Response) Descriptor() ([]byte, []int) { + return fileDescriptor_api_17a7876430d97ebd, []int{2} +} +func (m *Response) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Response.Unmarshal(m, b) +} +func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Response.Marshal(b, m, deterministic) +} +func (dst *Response) XXX_Merge(src proto.Message) { + xxx_messageInfo_Response.Merge(dst, src) +} +func (m *Response) XXX_Size() int { + return xxx_messageInfo_Response.Size(m) +} +func (m *Response) XXX_DiscardUnknown() { + xxx_messageInfo_Response.DiscardUnknown(m) +} + +var xxx_messageInfo_Response proto.InternalMessageInfo + +func (m *Response) GetStatusCode() int32 { + if m != nil { + return m.StatusCode + } + return 0 +} + +func (m *Response) GetHeader() map[string]*Pair { + if m != nil { + return m.Header + } + return nil +} + +func (m *Response) GetBody() string { + if m != nil { + return m.Body + } + return "" +} + +// A HTTP event as RPC +// Forwarded by the event handler +type Event struct { + // e.g login + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // uuid + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + // unix timestamp of event + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // event headers + Header map[string]*Pair `protobuf:"bytes,4,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // the event data + Data string `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Event) Reset() { *m = Event{} } +func (m *Event) String() string { return proto.CompactTextString(m) } +func (*Event) ProtoMessage() {} +func (*Event) Descriptor() ([]byte, []int) { + return fileDescriptor_api_17a7876430d97ebd, []int{3} +} +func (m *Event) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Event.Unmarshal(m, b) +} +func (m *Event) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Event.Marshal(b, m, deterministic) +} +func (dst *Event) XXX_Merge(src proto.Message) { + xxx_messageInfo_Event.Merge(dst, src) +} +func (m *Event) XXX_Size() int { + return xxx_messageInfo_Event.Size(m) +} +func (m *Event) XXX_DiscardUnknown() { + xxx_messageInfo_Event.DiscardUnknown(m) +} + +var xxx_messageInfo_Event proto.InternalMessageInfo + +func (m *Event) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Event) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Event) GetTimestamp() int64 { + if m != nil { + return m.Timestamp + } + return 0 +} + +func (m *Event) GetHeader() map[string]*Pair { + if m != nil { + return m.Header + } + return nil +} + +func (m *Event) GetData() string { + if m != nil { + return m.Data + } + return "" +} + +func init() { + proto.RegisterType((*Pair)(nil), "go.api.Pair") + proto.RegisterType((*Request)(nil), "go.api.Request") + proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Request.GetEntry") + proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Request.HeaderEntry") + proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Request.PostEntry") + proto.RegisterType((*Response)(nil), "go.api.Response") + proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Response.HeaderEntry") + proto.RegisterType((*Event)(nil), "go.api.Event") + proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Event.HeaderEntry") +} + +func init() { + proto.RegisterFile("github.com/micro/go-micro/api/proto/api.proto", fileDescriptor_api_17a7876430d97ebd) +} + +var fileDescriptor_api_17a7876430d97ebd = []byte{ + // 410 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x53, 0xc1, 0x6e, 0xd4, 0x30, + 0x10, 0x55, 0xe2, 0x24, 0x6d, 0x66, 0x11, 0x42, 0x3e, 0x20, 0x53, 0x2a, 0xb4, 0xca, 0x85, 0x15, + 0x52, 0x13, 0x68, 0x39, 0x20, 0xae, 0xb0, 0x2a, 0xc7, 0xca, 0x7f, 0xe0, 0x6d, 0xac, 0xc4, 0x62, + 0x13, 0x9b, 0xd8, 0xa9, 0xb4, 0x1f, 0xc7, 0x81, 0xcf, 0xe0, 0x6f, 0x90, 0x27, 0xde, 0xdd, 0xb2, + 0x5a, 0x2e, 0x74, 0x6f, 0x2f, 0xf6, 0x9b, 0x37, 0x6f, 0xde, 0x38, 0xf0, 0xb6, 0x51, 0xae, 0x1d, + 0x57, 0xe5, 0xbd, 0xee, 0xaa, 0x4e, 0xdd, 0x0f, 0xba, 0x6a, 0xf4, 0x95, 0x30, 0xaa, 0x32, 0x83, + 0x76, 0xba, 0x12, 0x46, 0x95, 0x88, 0x68, 0xd6, 0xe8, 0x52, 0x18, 0x55, 0xbc, 0x87, 0xe4, 0x4e, + 0xa8, 0x81, 0xbe, 0x00, 0xf2, 0x5d, 0x6e, 0x58, 0x34, 0x8f, 0x16, 0x39, 0xf7, 0x90, 0xbe, 0x84, + 0xec, 0x41, 0xac, 0x47, 0x69, 0x59, 0x3c, 0x27, 0x8b, 0x9c, 0x87, 0xaf, 0xe2, 0x17, 0x81, 0x33, + 0x2e, 0x7f, 0x8c, 0xd2, 0x3a, 0xcf, 0xe9, 0xa4, 0x6b, 0x75, 0x1d, 0x0a, 0xc3, 0x17, 0xa5, 0x90, + 0x18, 0xe1, 0x5a, 0x16, 0xe3, 0x29, 0x62, 0x7a, 0x03, 0x59, 0x2b, 0x45, 0x2d, 0x07, 0x46, 0xe6, + 0x64, 0x31, 0xbb, 0x7e, 0x5d, 0x4e, 0x16, 0xca, 0x20, 0x56, 0x7e, 0xc3, 0xdb, 0x65, 0xef, 0x86, + 0x0d, 0x0f, 0x54, 0xfa, 0x0e, 0x48, 0x23, 0x1d, 0x4b, 0xb0, 0x82, 0x1d, 0x56, 0xdc, 0x4a, 0x37, + 0xd1, 0x3d, 0x89, 0x5e, 0x41, 0x62, 0xb4, 0x75, 0x2c, 0x45, 0xf2, 0xab, 0x43, 0xf2, 0x9d, 0xb6, + 0x81, 0x8d, 0x34, 0xef, 0x71, 0xa5, 0xeb, 0x0d, 0xcb, 0x26, 0x8f, 0x1e, 0xfb, 0x14, 0xc6, 0x61, + 0xcd, 0xce, 0xa6, 0x14, 0xc6, 0x61, 0x7d, 0x71, 0x0b, 0xb3, 0x47, 0xbe, 0x8e, 0xc4, 0x54, 0x40, + 0x8a, 0xc1, 0xe0, 0xac, 0xb3, 0xeb, 0x67, 0xdb, 0xb6, 0x3e, 0x55, 0x3e, 0x5d, 0x7d, 0x8e, 0x3f, + 0x45, 0x17, 0x5f, 0xe1, 0x7c, 0x6b, 0xf7, 0x09, 0x2a, 0x4b, 0xc8, 0x77, 0x73, 0xfc, 0xbf, 0x4c, + 0xf1, 0x33, 0x82, 0x73, 0x2e, 0xad, 0xd1, 0xbd, 0x95, 0xf4, 0x0d, 0x80, 0x75, 0xc2, 0x8d, 0xf6, + 0x8b, 0xae, 0x25, 0xaa, 0xa5, 0xfc, 0xd1, 0x09, 0xfd, 0xb8, 0x5b, 0x5c, 0x8c, 0xc9, 0x5e, 0xee, + 0x93, 0x9d, 0x14, 0x8e, 0x6e, 0x6e, 0x1b, 0x2f, 0xd9, 0xc7, 0x7b, 0xb2, 0x30, 0x8b, 0xdf, 0x11, + 0xa4, 0xcb, 0x07, 0xd9, 0xe3, 0x16, 0x7b, 0xd1, 0xc9, 0x20, 0x82, 0x98, 0x3e, 0x87, 0x58, 0xd5, + 0xe1, 0xed, 0xc5, 0xaa, 0xa6, 0x97, 0x90, 0x3b, 0xd5, 0x49, 0xeb, 0x44, 0x67, 0xd0, 0x0f, 0xe1, + 0xfb, 0x03, 0xfa, 0x61, 0x37, 0x5e, 0xf2, 0xf7, 0xc3, 0xc1, 0x06, 0xff, 0x9a, 0xad, 0x16, 0x4e, + 0xb0, 0x74, 0x6a, 0xea, 0xf1, 0xc9, 0x66, 0x5b, 0x65, 0xf8, 0x83, 0xde, 0xfc, 0x09, 0x00, 0x00, + 0xff, 0xff, 0x7a, 0xb4, 0xd4, 0x8f, 0xcb, 0x03, 0x00, 0x00, +} diff --git a/api/proto/api.proto b/api/proto/api.proto new file mode 100644 index 00000000..fd0f9fec --- /dev/null +++ b/api/proto/api.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package go.api; + +message Pair { + string key = 1; + repeated string values = 2; +} + +// A HTTP request as RPC +// Forward by the api handler +message Request { + string method = 1; + string path = 2; + map header = 3; + map get = 4; + map post = 5; + string body = 6; // raw request body; if not application/x-www-form-urlencoded + string url = 7; +} + +// A HTTP response as RPC +// Expected response for the api handler +message Response { + int32 statusCode = 1; + map header = 2; + string body = 3; +} + +// A HTTP event as RPC +// Forwarded by the event handler +message Event { + // e.g login + string name = 1; + // uuid + string id = 2; + // unix timestamp of event + int64 timestamp = 3; + // event headers + map header = 4; + // the event data + string data = 5; +} diff --git a/api/resolver/grpc/grpc.go b/api/resolver/grpc/grpc.go new file mode 100644 index 00000000..9f23dcd4 --- /dev/null +++ b/api/resolver/grpc/grpc.go @@ -0,0 +1,38 @@ +// Package grpc resolves a grpc service like /greeter.Say/Hello to greeter service +package grpc + +import ( + "errors" + "net/http" + "strings" + + "github.com/micro/go-micro/api/resolver" +) + +type Resolver struct{} + +func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { + // /foo.Bar/Service + if req.URL.Path == "/" { + return nil, errors.New("unknown name") + } + // [foo.Bar, Service] + parts := strings.Split(req.URL.Path[1:], "/") + // [foo, Bar] + name := strings.Split(parts[0], ".") + // foo + return &resolver.Endpoint{ + Name: strings.Join(name[:len(name)-1], "."), + Host: req.Host, + Method: req.Method, + Path: req.URL.Path, + }, nil +} + +func (r *Resolver) String() string { + return "grpc" +} + +func NewResolver(opts ...resolver.Option) resolver.Resolver { + return &Resolver{} +} diff --git a/api/resolver/host/host.go b/api/resolver/host/host.go new file mode 100644 index 00000000..ba0d1945 --- /dev/null +++ b/api/resolver/host/host.go @@ -0,0 +1,27 @@ +// Package host resolves using http host +package host + +import ( + "net/http" + + "github.com/micro/go-micro/api/resolver" +) + +type Resolver struct{} + +func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { + return &resolver.Endpoint{ + Name: req.Host, + Host: req.Host, + Method: req.Method, + Path: req.URL.Path, + }, nil +} + +func (r *Resolver) String() string { + return "host" +} + +func NewResolver(opts ...resolver.Option) resolver.Resolver { + return &Resolver{} +} diff --git a/api/resolver/micro/micro.go b/api/resolver/micro/micro.go new file mode 100644 index 00000000..418f7a97 --- /dev/null +++ b/api/resolver/micro/micro.go @@ -0,0 +1,45 @@ +// Package micro provides a micro rpc resolver which prefixes a namespace +package micro + +import ( + "net/http" + + "github.com/micro/go-micro/api/resolver" +) + +// default resolver for legacy purposes +// it uses proxy routing to resolve names +// /foo becomes namespace.foo +// /v1/foo becomes namespace.v1.foo +type Resolver struct { + Options resolver.Options +} + +func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { + var name, method string + + switch r.Options.Handler { + // internal handlers + case "meta", "api", "rpc", "micro": + name, method = apiRoute(req.URL.Path) + default: + method = req.Method + name = proxyRoute(req.URL.Path) + } + + return &resolver.Endpoint{ + Name: name, + Method: method, + }, nil +} + +func (r *Resolver) String() string { + return "micro" +} + +// NewResolver creates a new micro resolver +func NewResolver(opts ...resolver.Option) resolver.Resolver { + return &Resolver{ + Options: resolver.NewOptions(opts...), + } +} diff --git a/api/resolver/micro/route.go b/api/resolver/micro/route.go new file mode 100644 index 00000000..96b128c8 --- /dev/null +++ b/api/resolver/micro/route.go @@ -0,0 +1,90 @@ +package micro + +import ( + "path" + "regexp" + "strings" +) + +var ( + proxyRe = regexp.MustCompile("^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$") + versionRe = regexp.MustCompilePOSIX("^v[0-9]+$") +) + +// Translates /foo/bar/zool into api service go.micro.api.foo method Bar.Zool +// Translates /foo/bar into api service go.micro.api.foo method Foo.Bar +func apiRoute(p string) (string, string) { + p = path.Clean(p) + p = strings.TrimPrefix(p, "/") + parts := strings.Split(p, "/") + + // If we've got two or less parts + // Use first part as service + // Use all parts as method + if len(parts) <= 2 { + name := parts[0] + return name, methodName(parts) + } + + // Treat /v[0-9]+ as versioning where we have 3 parts + // /v1/foo/bar => service: v1.foo method: Foo.bar + if len(parts) == 3 && versionRe.Match([]byte(parts[0])) { + name := strings.Join(parts[:len(parts)-1], ".") + return name, methodName(parts[len(parts)-2:]) + } + + // Service is everything minus last two parts + // Method is the last two parts + name := strings.Join(parts[:len(parts)-2], ".") + return name, methodName(parts[len(parts)-2:]) +} + +func proxyRoute(p string) string { + parts := strings.Split(p, "/") + if len(parts) < 2 { + return "" + } + + var service string + var alias string + + // /[service]/methods + if len(parts) > 2 { + // /v1/[service] + if versionRe.MatchString(parts[1]) { + service = parts[1] + "." + parts[2] + alias = parts[2] + } else { + service = parts[1] + alias = parts[1] + } + // /[service] + } else { + service = parts[1] + alias = parts[1] + } + + // check service name is valid + if !proxyRe.MatchString(alias) { + return "" + } + + return service +} + +func methodName(parts []string) string { + for i, part := range parts { + parts[i] = toCamel(part) + } + + return strings.Join(parts, ".") +} + +func toCamel(s string) string { + words := strings.Split(s, "-") + var out string + for _, word := range words { + out += strings.Title(word) + } + return out +} diff --git a/api/resolver/micro/route_test.go b/api/resolver/micro/route_test.go new file mode 100644 index 00000000..b2c61bce --- /dev/null +++ b/api/resolver/micro/route_test.go @@ -0,0 +1,130 @@ +package micro + +import ( + "testing" +) + +func TestApiRoute(t *testing.T) { + testData := []struct { + path string + service string + method string + }{ + { + "/foo/bar", + "foo", + "Foo.Bar", + }, + { + "/foo/foo/bar", + "foo", + "Foo.Bar", + }, + { + "/foo/bar/baz", + "foo", + "Bar.Baz", + }, + { + "/foo/bar/baz-xyz", + "foo", + "Bar.BazXyz", + }, + { + "/foo/bar/baz/cat", + "foo.bar", + "Baz.Cat", + }, + { + "/foo/bar/baz/cat/car", + "foo.bar.baz", + "Cat.Car", + }, + { + "/foo/fooBar/bazCat", + "foo", + "FooBar.BazCat", + }, + { + "/v1/foo/bar", + "v1.foo", + "Foo.Bar", + }, + { + "/v1/foo/bar/baz", + "v1.foo", + "Bar.Baz", + }, + { + "/v1/foo/bar/baz/cat", + "v1.foo.bar", + "Baz.Cat", + }, + } + + for _, d := range testData { + s, m := apiRoute(d.path) + if d.service != s { + t.Fatalf("Expected service: %s for path: %s got: %s %s", d.service, d.path, s, m) + } + if d.method != m { + t.Fatalf("Expected service: %s for path: %s got: %s", d.method, d.path, m) + } + } +} + +func TestProxyRoute(t *testing.T) { + testData := []struct { + path string + service string + }{ + // no namespace + { + "/f", + "f", + }, + { + "/f", + "f", + }, + { + "/f-b", + "f-b", + }, + { + "/foo/bar", + "foo", + }, + { + "/foo-bar", + "foo-bar", + }, + { + "/foo-bar-baz", + "foo-bar-baz", + }, + { + "/foo/bar/bar", + "foo", + }, + { + "/v1/foo/bar", + "v1.foo", + }, + { + "/v1/foo/bar/baz", + "v1.foo", + }, + { + "/v1/foo/bar/baz/cat", + "v1.foo", + }, + } + + for _, d := range testData { + s := proxyRoute(d.path) + if d.service != s { + t.Fatalf("Expected service: %s for path: %s got: %s", d.service, d.path, s) + } + } +} diff --git a/api/resolver/options.go b/api/resolver/options.go new file mode 100644 index 00000000..ce4b2e49 --- /dev/null +++ b/api/resolver/options.go @@ -0,0 +1,24 @@ +package resolver + +// NewOptions returns new initialised options +func NewOptions(opts ...Option) Options { + var options Options + for _, o := range opts { + o(&options) + } + return options +} + +// WithHandler sets the handler being used +func WithHandler(h string) Option { + return func(o *Options) { + o.Handler = h + } +} + +// WithNamespace sets the namespace being used +func WithNamespace(n string) Option { + return func(o *Options) { + o.Namespace = n + } +} diff --git a/api/resolver/path/path.go b/api/resolver/path/path.go new file mode 100644 index 00000000..635cbceb --- /dev/null +++ b/api/resolver/path/path.go @@ -0,0 +1,33 @@ +// Package path resolves using http path +package path + +import ( + "errors" + "net/http" + "strings" + + "github.com/micro/go-micro/api/resolver" +) + +type Resolver struct{} + +func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { + if req.URL.Path == "/" { + return nil, errors.New("unknown name") + } + parts := strings.Split(req.URL.Path[1:], "/") + return &resolver.Endpoint{ + Name: parts[0], + Host: req.Host, + Method: req.Method, + Path: req.URL.Path, + }, nil +} + +func (r *Resolver) String() string { + return "path" +} + +func NewResolver(opts ...resolver.Option) resolver.Resolver { + return &Resolver{} +} diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go new file mode 100644 index 00000000..2e23d62e --- /dev/null +++ b/api/resolver/resolver.go @@ -0,0 +1,31 @@ +// Package resolver resolves a http request to an endpoint +package resolver + +import ( + "net/http" +) + +// Resolver resolves requests to endpoints +type Resolver interface { + Resolve(r *http.Request) (*Endpoint, error) + String() string +} + +// Endpoint is the endpoint for a http request +type Endpoint struct { + // e.g greeter + Name string + // HTTP Host e.g example.com + Host string + // HTTP Methods e.g GET, POST + Method string + // HTTP Path e.g /greeter. + Path string +} + +type Options struct { + Handler string + Namespace string +} + +type Option func(o *Options) diff --git a/api/resolver/vpath/vpath.go b/api/resolver/vpath/vpath.go new file mode 100644 index 00000000..a9baeecb --- /dev/null +++ b/api/resolver/vpath/vpath.go @@ -0,0 +1,59 @@ +// Package vpath resolves using http path and recognised versioned urls +package vpath + +import ( + "errors" + "net/http" + "regexp" + "strings" + + "github.com/micro/go-micro/api/resolver" +) + +type Resolver struct{} + +var ( + re = regexp.MustCompile("^v[0-9]+$") +) + +func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { + if req.URL.Path == "/" { + return nil, errors.New("unknown name") + } + + parts := strings.Split(req.URL.Path[1:], "/") + + if len(parts) == 1 { + return &resolver.Endpoint{ + Name: parts[0], + Host: req.Host, + Method: req.Method, + Path: req.URL.Path, + }, nil + } + + // /v1/foo + if re.MatchString(parts[0]) { + return &resolver.Endpoint{ + Name: parts[1], + Host: req.Host, + Method: req.Method, + Path: req.URL.Path, + }, nil + } + + return &resolver.Endpoint{ + Name: parts[0], + Host: req.Host, + Method: req.Method, + Path: req.URL.Path, + }, nil +} + +func (r *Resolver) String() string { + return "path" +} + +func NewResolver(opts ...resolver.Option) resolver.Resolver { + return &Resolver{} +} diff --git a/api/router/options.go b/api/router/options.go new file mode 100644 index 00000000..f41499a3 --- /dev/null +++ b/api/router/options.go @@ -0,0 +1,61 @@ +package router + +import ( + "github.com/micro/go-micro/api/resolver" + "github.com/micro/go-micro/api/resolver/micro" + "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/registry" +) + +type Options struct { + Namespace string + Handler string + Registry registry.Registry + Resolver resolver.Resolver +} + +type Option func(o *Options) + +func NewOptions(opts ...Option) Options { + options := Options{ + Handler: "meta", + Registry: *cmd.DefaultOptions().Registry, + } + + for _, o := range opts { + o(&options) + } + + if options.Resolver == nil { + options.Resolver = micro.NewResolver( + resolver.WithHandler(options.Handler), + resolver.WithNamespace(options.Namespace), + ) + } + + return options +} + +func WithHandler(h string) Option { + return func(o *Options) { + o.Handler = h + } +} + +func WithNamespace(ns string) Option { + return func(o *Options) { + o.Namespace = ns + } +} + +func WithRegistry(r registry.Registry) Option { + return func(o *Options) { + o.Registry = r + } +} + +func WithResolver(r resolver.Resolver) Option { + return func(o *Options) { + o.Resolver = r + } +} diff --git a/api/router/registry/registry.go b/api/router/registry/registry.go new file mode 100644 index 00000000..368e31d4 --- /dev/null +++ b/api/router/registry/registry.go @@ -0,0 +1,393 @@ +// Package registry provides a dynamic api service router +package registry + +import ( + "errors" + "fmt" + "log" + "net/http" + "regexp" + "strings" + "sync" + "time" + + "github.com/micro/go-micro/api" + "github.com/micro/go-micro/api/router" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/registry/cache" +) + +// router is the default router +type registryRouter struct { + exit chan bool + opts router.Options + + // registry cache + rc cache.Cache + + sync.RWMutex + eps map[string]*api.Service +} + +func setNamespace(ns, name string) string { + ns = strings.TrimSpace(ns) + name = strings.TrimSpace(name) + + // no namespace + if len(ns) == 0 { + return name + } + + switch { + // has - suffix + case strings.HasSuffix(ns, "-"): + return strings.Replace(ns+name, ".", "-", -1) + // has . suffix + case strings.HasSuffix(ns, "."): + return ns + name + } + + // default join . + return strings.Join([]string{ns, name}, ".") +} + +func (r *registryRouter) isClosed() bool { + select { + case <-r.exit: + return true + default: + return false + } +} + +// refresh list of api services +func (r *registryRouter) refresh() { + var attempts int + + for { + services, err := r.opts.Registry.ListServices() + if err != nil { + attempts++ + log.Println("Error listing endpoints", err) + time.Sleep(time.Duration(attempts) * time.Second) + continue + } + + attempts = 0 + + // for each service, get service and store endpoints + for _, s := range services { + // only get services for this namespace + if !strings.HasPrefix(s.Name, r.opts.Namespace) { + continue + } + service, err := r.rc.GetService(s.Name) + if err != nil { + continue + } + r.store(service) + } + + // refresh list in 10 minutes... cruft + select { + case <-time.After(time.Minute * 10): + case <-r.exit: + return + } + } +} + +// process watch event +func (r *registryRouter) process(res *registry.Result) { + // skip these things + if res == nil || res.Service == nil || !strings.HasPrefix(res.Service.Name, r.opts.Namespace) { + return + } + + // get entry from cache + service, err := r.rc.GetService(res.Service.Name) + if err != nil { + return + } + + // update our local endpoints + r.store(service) +} + +// store local endpoint cache +func (r *registryRouter) store(services []*registry.Service) { + // endpoints + eps := map[string]*api.Service{} + + // services + names := map[string]bool{} + + // create a new endpoint mapping + for _, service := range services { + // set names we need later + names[service.Name] = true + + // map per endpoint + for _, endpoint := range service.Endpoints { + // create a key service:endpoint_name + key := fmt.Sprintf("%s:%s", service.Name, endpoint.Name) + // decode endpoint + end := api.Decode(endpoint.Metadata) + + // if we got nothing skip + if err := api.Validate(end); err != nil { + continue + } + + // try get endpoint + ep, ok := eps[key] + if !ok { + ep = &api.Service{Name: service.Name} + } + + // overwrite the endpoint + ep.Endpoint = end + // append services + ep.Services = append(ep.Services, service) + // store it + eps[key] = ep + } + } + + r.Lock() + defer r.Unlock() + + // delete any existing eps for services we know + for key, service := range r.eps { + // skip what we don't care about + if !names[service.Name] { + continue + } + + // ok we know this thing + // delete delete delete + delete(r.eps, key) + } + + // now set the eps we have + for name, endpoint := range eps { + r.eps[name] = endpoint + } +} + +// watch for endpoint changes +func (r *registryRouter) watch() { + var attempts int + + for { + if r.isClosed() { + return + } + + // watch for changes + w, err := r.opts.Registry.Watch() + if err != nil { + attempts++ + log.Println("Error watching endpoints", err) + time.Sleep(time.Duration(attempts) * time.Second) + continue + } + + ch := make(chan bool) + + go func() { + select { + case <-ch: + w.Stop() + case <-r.exit: + w.Stop() + } + }() + + // reset if we get here + attempts = 0 + + for { + // process next event + res, err := w.Next() + if err != nil { + log.Println("Error getting next endpoint", err) + close(ch) + break + } + r.process(res) + } + } +} + +func (r *registryRouter) Options() router.Options { + return r.opts +} + +func (r *registryRouter) Close() error { + select { + case <-r.exit: + return nil + default: + close(r.exit) + r.rc.Stop() + } + return nil +} + +func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) { + if r.isClosed() { + return nil, errors.New("router closed") + } + + r.RLock() + defer r.RUnlock() + + // use the first match + // TODO: weighted matching + for _, e := range r.eps { + ep := e.Endpoint + + // match + var pathMatch, hostMatch, methodMatch bool + + // 1. try method GET, POST, PUT, etc + // 2. try host example.com, foobar.com, etc + // 3. try path /foo/bar, /bar/baz, etc + + // 1. try match method + for _, m := range ep.Method { + if req.Method == m { + methodMatch = true + break + } + } + + // no match on method pass + if len(ep.Method) > 0 && !methodMatch { + continue + } + + // 2. try match host + for _, h := range ep.Host { + if req.Host == h { + hostMatch = true + break + } + } + + // no match on host pass + if len(ep.Host) > 0 && !hostMatch { + continue + } + + // 3. try match paths + for _, p := range ep.Path { + re, err := regexp.CompilePOSIX(p) + if err == nil && re.MatchString(req.URL.Path) { + pathMatch = true + break + } + } + + // no match pass + if len(ep.Path) > 0 && !pathMatch { + continue + } + + // TODO: Percentage traffic + + // we got here, so its a match + return e, nil + } + + // no match + return nil, errors.New("not found") +} + +func (r *registryRouter) Route(req *http.Request) (*api.Service, error) { + if r.isClosed() { + return nil, errors.New("router closed") + } + + // try get an endpoint + ep, err := r.Endpoint(req) + if err == nil { + return ep, nil + } + + // error not nil + // ignore that shit + // TODO: don't ignore that shit + + // get the service name + rp, err := r.opts.Resolver.Resolve(req) + if err != nil { + return nil, err + } + + // service name + name := setNamespace(r.opts.Namespace, rp.Name) + + // get service + services, err := r.rc.GetService(name) + if err != nil { + return nil, err + } + + // only use endpoint matching when the meta handler is set aka api.Default + switch r.opts.Handler { + // rpc handlers + case "meta", "api", "rpc": + handler := r.opts.Handler + + // set default handler to api + if r.opts.Handler == "meta" { + handler = "rpc" + } + + // construct api service + return &api.Service{ + Name: name, + Endpoint: &api.Endpoint{ + Name: rp.Method, + Handler: handler, + }, + Services: services, + }, nil + // http handler + case "http", "proxy", "web": + // construct api service + return &api.Service{ + Name: name, + Endpoint: &api.Endpoint{ + Name: req.URL.String(), + Handler: r.opts.Handler, + Host: []string{req.Host}, + Method: []string{req.Method}, + Path: []string{req.URL.Path}, + }, + Services: services, + }, nil + } + + return nil, errors.New("unknown handler") +} + +func newRouter(opts ...router.Option) *registryRouter { + options := router.NewOptions(opts...) + r := ®istryRouter{ + exit: make(chan bool), + opts: options, + rc: cache.New(options.Registry), + eps: make(map[string]*api.Service), + } + go r.watch() + go r.refresh() + return r +} + +// NewRouter returns the default router +func NewRouter(opts ...router.Option) router.Router { + return newRouter(opts...) +} diff --git a/api/router/registry/registry_test.go b/api/router/registry/registry_test.go new file mode 100644 index 00000000..b2e9a59e --- /dev/null +++ b/api/router/registry/registry_test.go @@ -0,0 +1,181 @@ +package registry + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + "github.com/micro/go-micro/api" +) + +func TestSetNamespace(t *testing.T) { + testCases := []struct { + namespace string + name string + expected string + }{ + // default dotted path + { + "go.micro.api", + "foo", + "go.micro.api.foo", + }, + // dotted end + { + "go.micro.api.", + "foo", + "go.micro.api.foo", + }, + // dashed end + { + "go-micro-api-", + "foo", + "go-micro-api-foo", + }, + // no namespace + { + "", + "foo", + "foo", + }, + { + "go-micro-api-", + "v2.foo", + "go-micro-api-v2-foo", + }, + } + + for _, test := range testCases { + name := setNamespace(test.namespace, test.name) + if name != test.expected { + t.Fatalf("expected name %s got %s", test.expected, name) + } + } +} + +func TestRouter(t *testing.T) { + r := newRouter() + + compare := func(expect, got []string) bool { + // no data to compare, return true + if len(expect) == 0 && len(got) == 0 { + return true + } + // no data expected but got some return false + if len(expect) == 0 && len(got) > 0 { + return false + } + + // compare expected with what we got + for _, e := range expect { + var seen bool + for _, g := range got { + if e == g { + seen = true + break + } + } + if !seen { + return false + } + } + + // we're done, return true + return true + } + + testData := []struct { + e *api.Endpoint + r *http.Request + m bool + }{ + { + e: &api.Endpoint{ + Name: "Foo.Bar", + Host: []string{"example.com"}, + Method: []string{"GET"}, + Path: []string{"/foo"}, + }, + r: &http.Request{ + Host: "example.com", + Method: "GET", + URL: &url.URL{ + Path: "/foo", + }, + }, + m: true, + }, + { + e: &api.Endpoint{ + Name: "Bar.Baz", + Host: []string{"example.com", "foo.com"}, + Method: []string{"GET", "POST"}, + Path: []string{"/foo/bar"}, + }, + r: &http.Request{ + Host: "foo.com", + Method: "POST", + URL: &url.URL{ + Path: "/foo/bar", + }, + }, + m: true, + }, + { + e: &api.Endpoint{ + Name: "Test.Cruft", + Host: []string{"example.com", "foo.com"}, + Method: []string{"GET", "POST"}, + Path: []string{"/xyz"}, + }, + r: &http.Request{ + Host: "fail.com", + Method: "DELETE", + URL: &url.URL{ + Path: "/test/fail", + }, + }, + m: false, + }, + } + + for _, d := range testData { + key := fmt.Sprintf("%s:%s", "test.service", d.e.Name) + r.eps[key] = &api.Service{ + Endpoint: d.e, + } + } + + for _, d := range testData { + e, err := r.Endpoint(d.r) + if d.m && err != nil { + t.Fatalf("expected match, got %v", err) + } + if !d.m && err == nil { + t.Fatal("expected error got match") + } + // skip testing the non match + if !d.m { + continue + } + + ep := e.Endpoint + + // test the match + if d.e.Name != ep.Name { + t.Fatalf("expected %v got %v", d.e.Name, ep.Name) + } + if ok := compare(d.e.Method, ep.Method); !ok { + t.Fatalf("expected %v got %v", d.e.Method, ep.Method) + } + if ok := compare(d.e.Path, ep.Path); !ok { + t.Fatalf("expected %v got %v", d.e.Path, ep.Path) + } + if ok := compare(d.e.Host, ep.Host); !ok { + t.Fatalf("expected %v got %v", d.e.Host, ep.Host) + } + + } + +} diff --git a/api/router/router.go b/api/router/router.go new file mode 100644 index 00000000..3135cf40 --- /dev/null +++ b/api/router/router.go @@ -0,0 +1,20 @@ +// Package router provides api service routing +package router + +import ( + "net/http" + + "github.com/micro/go-micro/api" +) + +// Router is used to determine an endpoint for a request +type Router interface { + // Returns options + Options() Options + // Stop the router + Close() error + // Endpoint returns an api.Service endpoint or an error if it does not exist + Endpoint(r *http.Request) (*api.Service, error) + // Route returns an api.Service route + Route(r *http.Request) (*api.Service, error) +} diff --git a/api/server/http/http.go b/api/server/http/http.go new file mode 100644 index 00000000..0990dd8f --- /dev/null +++ b/api/server/http/http.go @@ -0,0 +1,98 @@ +// Package http provides a http server with features; acme, cors, etc +package http + +import ( + "crypto/tls" + "net" + "net/http" + "os" + "sync" + + "github.com/gorilla/handlers" + "github.com/micro/go-micro/api/server" + "github.com/micro/go-micro/util/log" + "golang.org/x/crypto/acme/autocert" +) + +type httpServer struct { + mux *http.ServeMux + opts server.Options + + mtx sync.RWMutex + address string + exit chan chan error +} + +func NewServer(address string) server.Server { + return &httpServer{ + opts: server.Options{}, + mux: http.NewServeMux(), + address: address, + exit: make(chan chan error), + } +} + +func (s *httpServer) Address() string { + s.mtx.RLock() + defer s.mtx.RUnlock() + return s.address +} + +func (s *httpServer) Init(opts ...server.Option) error { + for _, o := range opts { + o(&s.opts) + } + return nil +} + +func (s *httpServer) Handle(path string, handler http.Handler) { + s.mux.Handle(path, handlers.CombinedLoggingHandler(os.Stdout, handler)) +} + +func (s *httpServer) Start() error { + var l net.Listener + var err error + + if s.opts.EnableACME { + // should we check the address to make sure its using :443? + l = autocert.NewListener(s.opts.ACMEHosts...) + } else if s.opts.EnableTLS && s.opts.TLSConfig != nil { + l, err = tls.Listen("tcp", s.address, s.opts.TLSConfig) + } else { + // otherwise plain listen + l, err = net.Listen("tcp", s.address) + } + if err != nil { + return err + } + + log.Logf("HTTP API Listening on %s", l.Addr().String()) + + s.mtx.Lock() + s.address = l.Addr().String() + s.mtx.Unlock() + + go func() { + if err := http.Serve(l, s.mux); err != nil { + // temporary fix + //log.Fatal(err) + } + }() + + go func() { + ch := <-s.exit + ch <- l.Close() + }() + + return nil +} + +func (s *httpServer) Stop() error { + ch := make(chan error) + s.exit <- ch + return <-ch +} + +func (s *httpServer) String() string { + return "http" +} diff --git a/api/server/http/http_test.go b/api/server/http/http_test.go new file mode 100644 index 00000000..bbee35a0 --- /dev/null +++ b/api/server/http/http_test.go @@ -0,0 +1,41 @@ +package http + +import ( + "fmt" + "io/ioutil" + "net/http" + "testing" +) + +func TestHTTPServer(t *testing.T) { + testResponse := "hello world" + + s := NewServer("localhost:0") + + s.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, testResponse) + })) + + if err := s.Start(); err != nil { + t.Fatal(err) + } + + rsp, err := http.Get(fmt.Sprintf("http://%s/", s.Address())) + if err != nil { + t.Fatal(err) + } + defer rsp.Body.Close() + + b, err := ioutil.ReadAll(rsp.Body) + if err != nil { + t.Fatal(err) + } + + if string(b) != testResponse { + t.Fatalf("Unexpected response, got %s, expected %s", string(b), testResponse) + } + + if err := s.Stop(); err != nil { + t.Fatal(err) + } +} diff --git a/api/server/options.go b/api/server/options.go new file mode 100644 index 00000000..cd47562f --- /dev/null +++ b/api/server/options.go @@ -0,0 +1,38 @@ +package server + +import ( + "crypto/tls" +) + +type Option func(o *Options) + +type Options struct { + EnableACME bool + EnableTLS bool + ACMEHosts []string + TLSConfig *tls.Config +} + +func ACMEHosts(hosts ...string) Option { + return func(o *Options) { + o.ACMEHosts = hosts + } +} + +func EnableACME(b bool) Option { + return func(o *Options) { + o.EnableACME = b + } +} + +func EnableTLS(b bool) Option { + return func(o *Options) { + o.EnableTLS = b + } +} + +func TLSConfig(t *tls.Config) Option { + return func(o *Options) { + o.TLSConfig = t + } +} diff --git a/api/server/server.go b/api/server/server.go new file mode 100644 index 00000000..b1d63e35 --- /dev/null +++ b/api/server/server.go @@ -0,0 +1,15 @@ +// Package server provides an API gateway server which handles inbound requests +package server + +import ( + "net/http" +) + +// Server serves api requests +type Server interface { + Address() string + Init(opts ...Option) error + Handle(path string, handler http.Handler) + Start() error + Stop() error +} diff --git a/client/grpc/README.md b/client/grpc/README.md new file mode 100644 index 00000000..87c0c90d --- /dev/null +++ b/client/grpc/README.md @@ -0,0 +1,25 @@ +# GRPC Client + +The grpc client is a [micro.Client](https://godoc.org/github.com/micro/go-micro/client#Client) compatible client. + +## Overview + +The client makes use of the [google.golang.org/grpc](google.golang.org/grpc) framework for the underlying communication mechanism. + +## Usage + +Specify the client to your micro service + +```go +import ( + "github.com/micro/go-micro" + "github.com/micro/go-plugins/client/grpc" +) + +func main() { + service := micro.NewService( + micro.Name("greeter"), + micro.Client(grpc.NewClient()), + ) +} +``` diff --git a/client/grpc/buffer.go b/client/grpc/buffer.go new file mode 100644 index 00000000..c43bb231 --- /dev/null +++ b/client/grpc/buffer.go @@ -0,0 +1,14 @@ +package grpc + +import ( + "bytes" +) + +type buffer struct { + *bytes.Buffer +} + +func (b *buffer) Close() error { + b.Buffer.Reset() + return nil +} diff --git a/client/grpc/codec.go b/client/grpc/codec.go new file mode 100644 index 00000000..4994db70 --- /dev/null +++ b/client/grpc/codec.go @@ -0,0 +1,98 @@ +package grpc + +import ( + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/json-iterator/go" + "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/codec/jsonrpc" + "github.com/micro/go-micro/codec/protorpc" + "google.golang.org/grpc/encoding" +) + +type jsonCodec struct{} +type protoCodec struct{} +type bytesCodec struct{} +type wrapCodec struct{ encoding.Codec } + +var ( + defaultGRPCCodecs = map[string]encoding.Codec{ + "application/json": jsonCodec{}, + "application/proto": protoCodec{}, + "application/protobuf": protoCodec{}, + "application/octet-stream": protoCodec{}, + "application/grpc+json": jsonCodec{}, + "application/grpc+proto": protoCodec{}, + "application/grpc+bytes": bytesCodec{}, + } + + defaultRPCCodecs = map[string]codec.NewCodec{ + "application/json": jsonrpc.NewCodec, + "application/json-rpc": jsonrpc.NewCodec, + "application/protobuf": protorpc.NewCodec, + "application/proto-rpc": protorpc.NewCodec, + "application/octet-stream": protorpc.NewCodec, + } + + json = jsoniter.ConfigCompatibleWithStandardLibrary +) + +// UseNumber fix unmarshal Number(8234567890123456789) to interface(8.234567890123457e+18) +func UseNumber() { + json = jsoniter.Config{ + UseNumber: true, + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, + }.Froze() +} + +func (w wrapCodec) String() string { + return w.Codec.Name() +} + +func (protoCodec) Marshal(v interface{}) ([]byte, error) { + return proto.Marshal(v.(proto.Message)) +} + +func (protoCodec) Unmarshal(data []byte, v interface{}) error { + return proto.Unmarshal(data, v.(proto.Message)) +} + +func (protoCodec) Name() string { + return "proto" +} + +func (bytesCodec) Marshal(v interface{}) ([]byte, error) { + b, ok := v.(*[]byte) + if !ok { + return nil, fmt.Errorf("failed to marshal: %v is not type of *[]byte", v) + } + return *b, nil +} + +func (bytesCodec) Unmarshal(data []byte, v interface{}) error { + b, ok := v.(*[]byte) + if !ok { + return fmt.Errorf("failed to unmarshal: %v is not type of *[]byte", v) + } + *b = data + return nil +} + +func (bytesCodec) Name() string { + return "bytes" +} + +func (jsonCodec) Marshal(v interface{}) ([]byte, error) { + return json.Marshal(v) +} + +func (jsonCodec) Unmarshal(data []byte, v interface{}) error { + return json.Unmarshal(data, v) +} + +func (jsonCodec) Name() string { + return "json" +} diff --git a/client/grpc/error.go b/client/grpc/error.go new file mode 100644 index 00000000..47832527 --- /dev/null +++ b/client/grpc/error.go @@ -0,0 +1,30 @@ +package grpc + +import ( + "github.com/micro/go-micro/errors" + "google.golang.org/grpc/status" +) + +func microError(err error) error { + // no error + switch err { + case nil: + return nil + } + + // micro error + if v, ok := err.(*errors.Error); ok { + return v + } + + // grpc error + if s, ok := status.FromError(err); ok { + if e := errors.Parse(s.Message()); e.Code > 0 { + return e // actually a micro error + } + return errors.InternalServerError("go.micro.client", s.Message()) + } + + // do nothing + return err +} diff --git a/client/grpc/grpc.go b/client/grpc/grpc.go new file mode 100644 index 00000000..fabc5fec --- /dev/null +++ b/client/grpc/grpc.go @@ -0,0 +1,541 @@ +// Package grpc provides a gRPC client +package grpc + +import ( + "bytes" + "context" + "crypto/tls" + "fmt" + "sync" + "time" + + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/errors" + "github.com/micro/go-micro/metadata" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/transport" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/encoding" + gmetadata "google.golang.org/grpc/metadata" +) + +type grpcClient struct { + once sync.Once + opts client.Options + pool *pool +} + +func init() { + encoding.RegisterCodec(jsonCodec{}) + encoding.RegisterCodec(bytesCodec{}) + + cmd.DefaultClients["grpc"] = NewClient +} + +// secure returns the dial option for whether its a secure or insecure connection +func (g *grpcClient) secure() grpc.DialOption { + if g.opts.Context != nil { + if v := g.opts.Context.Value(tlsAuth{}); v != nil { + tls := v.(*tls.Config) + creds := credentials.NewTLS(tls) + return grpc.WithTransportCredentials(creds) + } + } + return grpc.WithInsecure() +} + +func (g *grpcClient) next(request client.Request, opts client.CallOptions) (selector.Next, error) { + // return remote address + if len(opts.Address) > 0 { + return func() (*registry.Node, error) { + return ®istry.Node{ + Address: opts.Address, + }, nil + }, nil + } + + // get next nodes from the selector + next, err := g.opts.Selector.Select(request.Service(), opts.SelectOptions...) + if err != nil && err == selector.ErrNotFound { + return nil, errors.NotFound("go.micro.client", err.Error()) + } else if err != nil { + return nil, errors.InternalServerError("go.micro.client", err.Error()) + } + + return next, nil +} + +func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error { + address := node.Address + if node.Port > 0 { + address = fmt.Sprintf("%s:%d", address, node.Port) + } + + header := make(map[string]string) + if md, ok := metadata.FromContext(ctx); ok { + for k, v := range md { + header[k] = v + } + } + + // set timeout in nanoseconds + header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout) + // set the content type for the request + header["x-content-type"] = req.ContentType() + + md := gmetadata.New(header) + ctx = gmetadata.NewOutgoingContext(ctx, md) + + cf, err := g.newGRPCCodec(req.ContentType()) + if err != nil { + return errors.InternalServerError("go.micro.client", err.Error()) + } + + maxRecvMsgSize := g.maxRecvMsgSizeValue() + maxSendMsgSize := g.maxSendMsgSizeValue() + + var grr error + + cc, err := g.pool.getConn(address, grpc.WithDefaultCallOptions(grpc.CallCustomCodec(cf)), + grpc.WithTimeout(opts.DialTimeout), g.secure(), + grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(maxRecvMsgSize), + grpc.MaxCallSendMsgSize(maxSendMsgSize), + )) + if err != nil { + return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err)) + } + defer func() { + // defer execution of release + g.pool.release(address, cc, grr) + }() + + ch := make(chan error, 1) + + go func() { + err := cc.Invoke(ctx, methodToGRPC(req.Endpoint(), req.Body()), req.Body(), rsp, grpc.CallContentSubtype(cf.String())) + ch <- microError(err) + }() + + select { + case err := <-ch: + grr = err + case <-ctx.Done(): + grr = ctx.Err() + } + + return grr +} + +func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client.Request, opts client.CallOptions) (client.Stream, error) { + address := node.Address + if node.Port > 0 { + address = fmt.Sprintf("%s:%d", address, node.Port) + } + + header := make(map[string]string) + if md, ok := metadata.FromContext(ctx); ok { + for k, v := range md { + header[k] = v + } + } + + // set timeout in nanoseconds + header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout) + // set the content type for the request + header["x-content-type"] = req.ContentType() + + md := gmetadata.New(header) + ctx = gmetadata.NewOutgoingContext(ctx, md) + + cf, err := g.newGRPCCodec(req.ContentType()) + if err != nil { + return nil, errors.InternalServerError("go.micro.client", err.Error()) + } + + var dialCtx context.Context + var cancel context.CancelFunc + if opts.DialTimeout >= 0 { + dialCtx, cancel = context.WithTimeout(ctx, opts.DialTimeout) + } else { + dialCtx, cancel = context.WithCancel(ctx) + } + defer cancel() + cc, err := grpc.DialContext(dialCtx, address, grpc.WithDefaultCallOptions(grpc.CallCustomCodec(cf)), g.secure()) + if err != nil { + return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err)) + } + + desc := &grpc.StreamDesc{ + StreamName: req.Service() + req.Endpoint(), + ClientStreams: true, + ServerStreams: true, + } + + st, err := cc.NewStream(ctx, desc, methodToGRPC(req.Endpoint(), req.Body()), grpc.CallContentSubtype(cf.String())) + if err != nil { + return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error creating stream: %v", err)) + } + + return &grpcStream{ + context: ctx, + request: req, + stream: st, + conn: cc, + }, nil +} + +func (g *grpcClient) maxRecvMsgSizeValue() int { + if g.opts.Context == nil { + return DefaultMaxRecvMsgSize + } + v := g.opts.Context.Value(maxRecvMsgSizeKey{}) + if v == nil { + return DefaultMaxRecvMsgSize + } + return v.(int) +} + +func (g *grpcClient) maxSendMsgSizeValue() int { + if g.opts.Context == nil { + return DefaultMaxSendMsgSize + } + v := g.opts.Context.Value(maxSendMsgSizeKey{}) + if v == nil { + return DefaultMaxSendMsgSize + } + return v.(int) +} + +func (g *grpcClient) newGRPCCodec(contentType string) (grpc.Codec, error) { + codecs := make(map[string]encoding.Codec) + if g.opts.Context != nil { + if v := g.opts.Context.Value(codecsKey{}); v != nil { + codecs = v.(map[string]encoding.Codec) + } + } + if c, ok := codecs[contentType]; ok { + return wrapCodec{c}, nil + } + if c, ok := defaultGRPCCodecs[contentType]; ok { + return wrapCodec{c}, nil + } + return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) +} + +func (g *grpcClient) newCodec(contentType string) (codec.NewCodec, error) { + if c, ok := g.opts.Codecs[contentType]; ok { + return c, nil + } + if cf, ok := defaultRPCCodecs[contentType]; ok { + return cf, nil + } + return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) +} + +func (g *grpcClient) Init(opts ...client.Option) error { + size := g.opts.PoolSize + ttl := g.opts.PoolTTL + + for _, o := range opts { + o(&g.opts) + } + + // update pool configuration if the options changed + if size != g.opts.PoolSize || ttl != g.opts.PoolTTL { + g.pool.Lock() + g.pool.size = g.opts.PoolSize + g.pool.ttl = int64(g.opts.PoolTTL.Seconds()) + g.pool.Unlock() + } + + return nil +} + +func (g *grpcClient) Options() client.Options { + return g.opts +} + +func (g *grpcClient) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message { + return newGRPCPublication(topic, msg, "application/octet-stream") +} + +func (g *grpcClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request { + return newGRPCRequest(service, method, req, g.opts.ContentType, reqOpts...) +} + +func (g *grpcClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { + // make a copy of call opts + callOpts := g.opts.CallOptions + for _, opt := range opts { + opt(&callOpts) + } + + next, err := g.next(req, callOpts) + if err != nil { + return err + } + + // check if we already have a deadline + d, ok := ctx.Deadline() + if !ok { + // no deadline so we create a new one + ctx, _ = context.WithTimeout(ctx, callOpts.RequestTimeout) + } else { + // got a deadline so no need to setup context + // but we need to set the timeout we pass along + opt := client.WithRequestTimeout(time.Until(d)) + opt(&callOpts) + } + + // should we noop right here? + select { + case <-ctx.Done(): + return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) + default: + } + + // make copy of call method + gcall := g.call + + // wrap the call in reverse + for i := len(callOpts.CallWrappers); i > 0; i-- { + gcall = callOpts.CallWrappers[i-1](gcall) + } + + // return errors.New("go.micro.client", "request timeout", 408) + call := func(i int) error { + // call backoff first. Someone may want an initial start delay + t, err := callOpts.Backoff(ctx, req, i) + if err != nil { + return errors.InternalServerError("go.micro.client", err.Error()) + } + + // only sleep if greater than 0 + if t.Seconds() > 0 { + time.Sleep(t) + } + + // select next node + node, err := next() + if err != nil && err == selector.ErrNotFound { + return errors.NotFound("go.micro.client", err.Error()) + } else if err != nil { + return errors.InternalServerError("go.micro.client", err.Error()) + } + + // make the call + err = gcall(ctx, node, req, rsp, callOpts) + g.opts.Selector.Mark(req.Service(), node, err) + return err + } + + ch := make(chan error, callOpts.Retries+1) + var gerr error + + for i := 0; i <= callOpts.Retries; i++ { + go func() { + ch <- call(i) + }() + + select { + case <-ctx.Done(): + return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) + case err := <-ch: + // if the call succeeded lets bail early + if err == nil { + return nil + } + + retry, rerr := callOpts.Retry(ctx, req, i, err) + if rerr != nil { + return rerr + } + + if !retry { + return err + } + + gerr = err + } + } + + return gerr +} + +func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { + // make a copy of call opts + callOpts := g.opts.CallOptions + for _, opt := range opts { + opt(&callOpts) + } + + next, err := g.next(req, callOpts) + if err != nil { + return nil, err + } + + // #200 - streams shouldn't have a request timeout set on the context + + // should we noop right here? + select { + case <-ctx.Done(): + return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) + default: + } + + call := func(i int) (client.Stream, error) { + // call backoff first. Someone may want an initial start delay + t, err := callOpts.Backoff(ctx, req, i) + if err != nil { + return nil, errors.InternalServerError("go.micro.client", err.Error()) + } + + // only sleep if greater than 0 + if t.Seconds() > 0 { + time.Sleep(t) + } + + node, err := next() + if err != nil && err == selector.ErrNotFound { + return nil, errors.NotFound("go.micro.client", err.Error()) + } else if err != nil { + return nil, errors.InternalServerError("go.micro.client", err.Error()) + } + + stream, err := g.stream(ctx, node, req, callOpts) + g.opts.Selector.Mark(req.Service(), node, err) + return stream, err + } + + type response struct { + stream client.Stream + err error + } + + ch := make(chan response, callOpts.Retries+1) + var grr error + + for i := 0; i <= callOpts.Retries; i++ { + go func() { + s, err := call(i) + ch <- response{s, err} + }() + + select { + case <-ctx.Done(): + return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) + case rsp := <-ch: + // if the call succeeded lets bail early + if rsp.err == nil { + return rsp.stream, nil + } + + retry, rerr := callOpts.Retry(ctx, req, i, err) + if rerr != nil { + return nil, rerr + } + + if !retry { + return nil, rsp.err + } + + grr = rsp.err + } + } + + return nil, grr +} + +func (g *grpcClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error { + md, ok := metadata.FromContext(ctx) + if !ok { + md = make(map[string]string) + } + md["Content-Type"] = p.ContentType() + + cf, err := g.newCodec(p.ContentType()) + if err != nil { + return errors.InternalServerError("go.micro.client", err.Error()) + } + + b := &buffer{bytes.NewBuffer(nil)} + if err := cf(b).Write(&codec.Message{Type: codec.Publication}, p.Payload()); err != nil { + return errors.InternalServerError("go.micro.client", err.Error()) + } + + g.once.Do(func() { + g.opts.Broker.Connect() + }) + + return g.opts.Broker.Publish(p.Topic(), &broker.Message{ + Header: md, + Body: b.Bytes(), + }) +} + +func (g *grpcClient) String() string { + return "grpc" +} + +func newClient(opts ...client.Option) client.Client { + options := client.Options{ + Codecs: make(map[string]codec.NewCodec), + CallOptions: client.CallOptions{ + Backoff: client.DefaultBackoff, + Retry: client.DefaultRetry, + Retries: client.DefaultRetries, + RequestTimeout: client.DefaultRequestTimeout, + DialTimeout: transport.DefaultDialTimeout, + }, + PoolSize: client.DefaultPoolSize, + PoolTTL: client.DefaultPoolTTL, + } + + for _, o := range opts { + o(&options) + } + + if len(options.ContentType) == 0 { + options.ContentType = "application/grpc+proto" + } + + if options.Broker == nil { + options.Broker = broker.DefaultBroker + } + + if options.Registry == nil { + options.Registry = registry.DefaultRegistry + } + + if options.Selector == nil { + options.Selector = selector.NewSelector( + selector.Registry(options.Registry), + ) + } + + rc := &grpcClient{ + once: sync.Once{}, + opts: options, + pool: newPool(options.PoolSize, options.PoolTTL), + } + + c := client.Client(rc) + + // wrap in reverse + for i := len(options.Wrappers); i > 0; i-- { + c = options.Wrappers[i-1](c) + } + + return c +} + +func NewClient(opts ...client.Option) client.Client { + return newClient(opts...) +} diff --git a/client/grpc/grpc_pool.go b/client/grpc/grpc_pool.go new file mode 100644 index 00000000..ea084e74 --- /dev/null +++ b/client/grpc/grpc_pool.go @@ -0,0 +1,83 @@ +package grpc + +import ( + "sync" + "time" + + "google.golang.org/grpc" +) + +type pool struct { + size int + ttl int64 + + sync.Mutex + conns map[string][]*poolConn +} + +type poolConn struct { + *grpc.ClientConn + created int64 +} + +func newPool(size int, ttl time.Duration) *pool { + return &pool{ + size: size, + ttl: int64(ttl.Seconds()), + conns: make(map[string][]*poolConn), + } +} + +func (p *pool) getConn(addr string, opts ...grpc.DialOption) (*poolConn, error) { + p.Lock() + conns := p.conns[addr] + now := time.Now().Unix() + + // while we have conns check age and then return one + // otherwise we'll create a new conn + for len(conns) > 0 { + conn := conns[len(conns)-1] + conns = conns[:len(conns)-1] + p.conns[addr] = conns + + // if conn is old kill it and move on + if d := now - conn.created; d > p.ttl { + conn.ClientConn.Close() + continue + } + + // we got a good conn, lets unlock and return it + p.Unlock() + + return conn, nil + } + + p.Unlock() + + // create new conn + cc, err := grpc.Dial(addr, opts...) + if err != nil { + return nil, err + } + + return &poolConn{cc, time.Now().Unix()}, nil +} + +func (p *pool) release(addr string, conn *poolConn, err error) { + // don't store the conn if it has errored + if err != nil { + conn.ClientConn.Close() + return + } + + // otherwise put it back for reuse + p.Lock() + conns := p.conns[addr] + if len(conns) >= p.size { + p.Unlock() + conn.ClientConn.Close() + return + } + p.conns[addr] = append(conns, conn) + p.Unlock() +} diff --git a/client/grpc/grpc_pool_test.go b/client/grpc/grpc_pool_test.go new file mode 100644 index 00000000..955e18c5 --- /dev/null +++ b/client/grpc/grpc_pool_test.go @@ -0,0 +1,64 @@ +package grpc + +import ( + "net" + "testing" + "time" + + "context" + "google.golang.org/grpc" + pgrpc "google.golang.org/grpc" + pb "google.golang.org/grpc/examples/helloworld/helloworld" +) + +func testPool(t *testing.T, size int, ttl time.Duration) { + // setup server + l, err := net.Listen("tcp", ":0") + if err != nil { + t.Fatalf("failed to listen: %v", err) + } + defer l.Close() + + s := pgrpc.NewServer() + pb.RegisterGreeterServer(s, &greeterServer{}) + + go s.Serve(l) + defer s.Stop() + + // zero pool + p := newPool(size, ttl) + + for i := 0; i < 10; i++ { + // get a conn + cc, err := p.getConn(l.Addr().String(), grpc.WithInsecure()) + if err != nil { + t.Fatal(err) + } + + rsp := pb.HelloReply{} + + err = cc.Invoke(context.TODO(), "/helloworld.Greeter/SayHello", &pb.HelloRequest{Name: "John"}, &rsp) + if err != nil { + t.Fatal(err) + } + + if rsp.Message != "Hello John" { + t.Fatalf("Got unexpected response %v", rsp.Message) + } + + // release the conn + p.release(l.Addr().String(), cc, nil) + + p.Lock() + if i := len(p.conns[l.Addr().String()]); i > size { + p.Unlock() + t.Fatalf("pool size %d is greater than expected %d", i, size) + } + p.Unlock() + } +} + +func TestGRPCPool(t *testing.T) { + testPool(t, 0, time.Minute) + testPool(t, 2, time.Minute) +} diff --git a/client/grpc/grpc_test.go b/client/grpc/grpc_test.go new file mode 100644 index 00000000..574058cb --- /dev/null +++ b/client/grpc/grpc_test.go @@ -0,0 +1,91 @@ +package grpc + +import ( + "context" + "net" + "strconv" + "strings" + "testing" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/registry/memory" + "github.com/micro/go-micro/selector" + pgrpc "google.golang.org/grpc" + pb "google.golang.org/grpc/examples/helloworld/helloworld" +) + +// server is used to implement helloworld.GreeterServer. +type greeterServer struct{} + +// SayHello implements helloworld.GreeterServer +func (g *greeterServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { + return &pb.HelloReply{Message: "Hello " + in.Name}, nil +} + +func TestGRPCClient(t *testing.T) { + l, err := net.Listen("tcp", ":0") + if err != nil { + t.Fatalf("failed to listen: %v", err) + } + defer l.Close() + + s := pgrpc.NewServer() + pb.RegisterGreeterServer(s, &greeterServer{}) + + go s.Serve(l) + defer s.Stop() + + parts := strings.Split(l.Addr().String(), ":") + port, _ := strconv.Atoi(parts[len(parts)-1]) + addr := strings.Join(parts[:len(parts)-1], ":") + + // create mock registry + r := memory.NewRegistry() + + // register service + r.Register(®istry.Service{ + Name: "test", + Version: "test", + Nodes: []*registry.Node{ + ®istry.Node{ + Id: "test-1", + Address: addr, + Port: port, + }, + }, + }) + + // create selector + se := selector.NewSelector( + selector.Registry(r), + ) + + // create client + c := NewClient( + client.Registry(r), + client.Selector(se), + ) + + testMethods := []string{ + "/helloworld.Greeter/SayHello", + "Greeter.SayHello", + } + + for _, method := range testMethods { + req := c.NewRequest("test", method, &pb.HelloRequest{ + Name: "John", + }) + + rsp := pb.HelloReply{} + + err = c.Call(context.TODO(), req, &rsp) + if err != nil { + t.Fatal(err) + } + + if rsp.Message != "Hello John" { + t.Fatalf("Got unexpected response %v", rsp.Message) + } + } +} diff --git a/client/grpc/message.go b/client/grpc/message.go new file mode 100644 index 00000000..6938064e --- /dev/null +++ b/client/grpc/message.go @@ -0,0 +1,40 @@ +package grpc + +import ( + "github.com/micro/go-micro/client" +) + +type grpcPublication struct { + topic string + contentType string + payload interface{} +} + +func newGRPCPublication(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message { + var options client.MessageOptions + for _, o := range opts { + o(&options) + } + + if len(options.ContentType) > 0 { + contentType = options.ContentType + } + + return &grpcPublication{ + payload: payload, + topic: topic, + contentType: contentType, + } +} + +func (g *grpcPublication) ContentType() string { + return g.contentType +} + +func (g *grpcPublication) Topic() string { + return g.topic +} + +func (g *grpcPublication) Payload() interface{} { + return g.payload +} diff --git a/client/grpc/options.go b/client/grpc/options.go new file mode 100644 index 00000000..c702ade3 --- /dev/null +++ b/client/grpc/options.go @@ -0,0 +1,74 @@ +// Package grpc provides a gRPC options +package grpc + +import ( + "context" + "crypto/tls" + + "github.com/micro/go-micro/client" + "google.golang.org/grpc/encoding" +) + +var ( + // DefaultMaxRecvMsgSize maximum message that client can receive + // (4 MB). + DefaultMaxRecvMsgSize = 1024 * 1024 * 4 + + // DefaultMaxSendMsgSize maximum message that client can send + // (4 MB). + DefaultMaxSendMsgSize = 1024 * 1024 * 4 +) + +type codecsKey struct{} +type tlsAuth struct{} +type maxRecvMsgSizeKey struct{} +type maxSendMsgSizeKey struct{} + +// gRPC Codec to be used to encode/decode requests for a given content type +func Codec(contentType string, c encoding.Codec) client.Option { + return func(o *client.Options) { + codecs := make(map[string]encoding.Codec) + if o.Context == nil { + o.Context = context.Background() + } + if v := o.Context.Value(codecsKey{}); v != nil { + codecs = v.(map[string]encoding.Codec) + } + codecs[contentType] = c + o.Context = context.WithValue(o.Context, codecsKey{}, codecs) + } +} + +// AuthTLS should be used to setup a secure authentication using TLS +func AuthTLS(t *tls.Config) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, tlsAuth{}, t) + } +} + +// +// MaxRecvMsgSize set the maximum size of message that client can receive. +// +func MaxRecvMsgSize(s int) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, maxRecvMsgSizeKey{}, s) + } +} + +// +// MaxSendMsgSize set the maximum size of message that client can send. +// +func MaxSendMsgSize(s int) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, maxSendMsgSizeKey{}, s) + } +} diff --git a/client/grpc/request.go b/client/grpc/request.go new file mode 100644 index 00000000..28409c44 --- /dev/null +++ b/client/grpc/request.go @@ -0,0 +1,92 @@ +package grpc + +import ( + "fmt" + "reflect" + "strings" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/codec" +) + +type grpcRequest struct { + service string + method string + contentType string + request interface{} + opts client.RequestOptions +} + +func methodToGRPC(method string, request interface{}) string { + // no method or already grpc method + if len(method) == 0 || method[0] == '/' { + return method + } + // can't operate on nil request + t := reflect.TypeOf(request) + if t == nil { + return method + } + // dereference + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + // get package name + pParts := strings.Split(t.PkgPath(), "/") + pkg := pParts[len(pParts)-1] + // assume method is Foo.Bar + mParts := strings.Split(method, ".") + if len(mParts) != 2 { + return method + } + // return /pkg.Foo/Bar + return fmt.Sprintf("/%s.%s/%s", pkg, mParts[0], mParts[1]) +} + +func newGRPCRequest(service, method string, request interface{}, contentType string, reqOpts ...client.RequestOption) client.Request { + var opts client.RequestOptions + for _, o := range reqOpts { + o(&opts) + } + + // set the content-type specified + if len(opts.ContentType) > 0 { + contentType = opts.ContentType + } + + return &grpcRequest{ + service: service, + method: method, + request: request, + contentType: contentType, + opts: opts, + } +} + +func (g *grpcRequest) ContentType() string { + return g.contentType +} + +func (g *grpcRequest) Service() string { + return g.service +} + +func (g *grpcRequest) Method() string { + return g.method +} + +func (g *grpcRequest) Endpoint() string { + return g.method +} + +func (g *grpcRequest) Codec() codec.Writer { + return nil +} + +func (g *grpcRequest) Body() interface{} { + return g.request +} + +func (g *grpcRequest) Stream() bool { + return g.opts.Stream +} diff --git a/client/grpc/request_test.go b/client/grpc/request_test.go new file mode 100644 index 00000000..eab3a3a1 --- /dev/null +++ b/client/grpc/request_test.go @@ -0,0 +1,48 @@ +package grpc + +import ( + "testing" + + pb "google.golang.org/grpc/examples/helloworld/helloworld" +) + +func TestMethodToGRPC(t *testing.T) { + testData := []struct { + method string + expect string + request interface{} + }{ + { + "Greeter.SayHello", + "/helloworld.Greeter/SayHello", + new(pb.HelloRequest), + }, + { + "/helloworld.Greeter/SayHello", + "/helloworld.Greeter/SayHello", + new(pb.HelloRequest), + }, + { + "Greeter.SayHello", + "/helloworld.Greeter/SayHello", + pb.HelloRequest{}, + }, + { + "/helloworld.Greeter/SayHello", + "/helloworld.Greeter/SayHello", + pb.HelloRequest{}, + }, + { + "Greeter.SayHello", + "Greeter.SayHello", + nil, + }, + } + + for _, d := range testData { + method := methodToGRPC(d.method, d.request) + if method != d.expect { + t.Fatalf("expected %s got %s", d.expect, method) + } + } +} diff --git a/client/grpc/stream.go b/client/grpc/stream.go new file mode 100644 index 00000000..bc00dbf8 --- /dev/null +++ b/client/grpc/stream.go @@ -0,0 +1,77 @@ +package grpc + +import ( + "context" + "io" + "sync" + + "github.com/micro/go-micro/client" + "google.golang.org/grpc" +) + +// Implements the streamer interface +type grpcStream struct { + sync.RWMutex + err error + conn *grpc.ClientConn + request client.Request + stream grpc.ClientStream + context context.Context +} + +func (g *grpcStream) Context() context.Context { + return g.context +} + +func (g *grpcStream) Request() client.Request { + return g.request +} + +func (g *grpcStream) Response() client.Response { + return nil +} + +func (g *grpcStream) Send(msg interface{}) error { + if err := g.stream.SendMsg(msg); err != nil { + g.setError(err) + return err + } + return nil +} + +func (g *grpcStream) Recv(msg interface{}) (err error) { + defer g.setError(err) + if err = g.stream.RecvMsg(msg); err != nil { + if err == io.EOF { + // #202 - inconsistent gRPC stream behavior + // the only way to tell if the stream is done is when we get a EOF on the Recv + // here we should close the underlying gRPC ClientConn + closeErr := g.conn.Close() + if closeErr != nil { + err = closeErr + } + } + } + return +} + +func (g *grpcStream) Error() error { + g.RLock() + defer g.RUnlock() + return g.err +} + +func (g *grpcStream) setError(e error) { + g.Lock() + g.err = e + g.Unlock() +} + +// Close the gRPC send stream +// #202 - inconsistent gRPC stream behavior +// The underlying gRPC stream should not be closed here since the +// stream should still be able to receive after this function call +// TODO: should the conn be closed in another way? +func (g *grpcStream) Close() error { + return g.stream.CloseSend() +} diff --git a/go.mod b/go.mod index 6352ae05..47ad8261 100644 --- a/go.mod +++ b/go.mod @@ -1,119 +1,55 @@ module github.com/micro/go-micro require ( - cloud.google.com/go v0.39.0 // indirect github.com/BurntSushi/toml v0.3.1 - github.com/OneOfOne/xxhash v1.2.5 // indirect - github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect - github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect - github.com/armon/go-radix v1.0.0 // indirect github.com/beevik/ntp v0.2.0 github.com/bitly/go-simplejson v0.5.0 - github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 github.com/bwmarrin/discordgo v0.19.0 - github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect - github.com/coreos/bbolt v1.3.2 // indirect github.com/coreos/etcd v3.3.13+incompatible - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect - github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect - github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect - github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b // indirect - github.com/emirpasic/gods v1.12.0 // indirect github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c github.com/fsnotify/fsnotify v1.4.7 github.com/fsouza/go-dockerclient v1.4.1 - github.com/garyburd/redigo v1.6.0 // indirect github.com/ghodss/yaml v1.0.0 - github.com/gliderlabs/ssh v0.1.4 // indirect github.com/go-log/log v0.1.0 github.com/go-redsync/redsync v1.2.0 - github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect - github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect - github.com/golang/mock v1.3.1 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/protobuf v1.3.1 github.com/gomodule/redigo v2.0.0+incompatible - github.com/google/btree v1.0.0 // indirect - github.com/google/pprof v0.0.0-20190515194954-54271f7e092f // indirect github.com/google/uuid v1.1.1 - github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect + github.com/gorilla/handlers v1.4.0 + github.com/gorilla/websocket v1.4.0 + github.com/grpc-ecosystem/grpc-gateway v1.9.0 + github.com/hashicorp/consul v1.5.1 github.com/hashicorp/consul/api v1.1.0 - github.com/hashicorp/go-immutable-radix v1.1.0 // indirect - github.com/hashicorp/go-msgpack v0.5.5 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 - github.com/hashicorp/mdns v1.0.1 // indirect github.com/hashicorp/memberlist v0.1.4 - github.com/hashicorp/serf v0.8.3 // indirect github.com/imdario/mergo v0.3.7 - github.com/jonboulle/clockwork v0.1.0 // indirect - github.com/json-iterator/go v1.1.6 // indirect - github.com/kisielk/errcheck v1.2.0 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect - github.com/kr/pty v1.1.4 // indirect - github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect - github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 // indirect - github.com/mattn/go-colorable v0.1.2 // indirect + github.com/joncalhoun/qson v0.0.0-20170526102502-8a9cab3a62b1 + github.com/json-iterator/go v1.1.6 github.com/micro/cli v0.2.0 + github.com/micro/examples v0.1.0 + github.com/micro/go-plugins v1.1.0 github.com/micro/mdns v0.1.0 - github.com/miekg/dns v1.1.13 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/micro/micro v1.3.0 github.com/mitchellh/hashstructure v1.0.0 - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/nats-io/gnatsd v1.4.1 // indirect - github.com/nats-io/go-nats v1.7.2 // indirect github.com/nats-io/nats.go v1.7.2 - github.com/nats-io/nkeys v0.0.2 // indirect - github.com/nats-io/nuid v1.0.1 // indirect github.com/nlopes/slack v0.5.0 - github.com/onsi/ginkgo v1.8.0 // indirect - github.com/onsi/gomega v1.5.0 // indirect github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.8.1 - github.com/posener/complete v1.2.1 // indirect - github.com/prometheus/client_golang v0.9.3 // indirect - github.com/prometheus/common v0.4.1 // indirect - github.com/prometheus/procfs v0.0.1 // indirect - github.com/prometheus/tsdb v0.8.0 // indirect - github.com/rogpeppe/fastuuid v1.1.0 // indirect - github.com/sirupsen/logrus v1.4.2 // indirect - github.com/soheilhy/cmux v0.1.4 // indirect - github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/stretchr/objx v0.2.0 // indirect - github.com/technoweenie/multipartstreamer v1.0.1 // indirect - github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect - github.com/xanzy/ssh-agent v0.2.1 // indirect - github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - go.etcd.io/bbolt v1.3.2 // indirect go.etcd.io/etcd v3.3.13+incompatible - go.opencensus.io v0.22.0 // indirect - go.uber.org/atomic v1.4.0 // indirect - go.uber.org/multierr v1.1.0 // indirect - go.uber.org/zap v1.10.0 // indirect - golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 // indirect - golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 // indirect - golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff // indirect - golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect - golang.org/x/mobile v0.0.0-20190509164839-32b2708ab171 // indirect - golang.org/x/net v0.0.0-20190522155817-f3200d17e092 - golang.org/x/oauth2 v0.0.0-20190523182746-aaccbc9213b0 // indirect - golang.org/x/sys v0.0.0-20190531132440-69e3a3a65b5b // indirect - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect - golang.org/x/tools v0.0.0-20190530215528-75312fb06703 // indirect - google.golang.org/appengine v1.6.0 // indirect - google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 // indirect - google.golang.org/grpc v1.21.0 // indirect - gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a // indirect + golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 + golang.org/x/mod v0.1.0 // indirect + golang.org/x/net v0.0.0-20190603091049-60506f45cf65 + golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect + golang.org/x/tools v0.0.0-20190603152906-08e0b306e832 // indirect + google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 + google.golang.org/grpc v1.21.0 + gopkg.in/go-playground/validator.v9 v9.29.0 gopkg.in/redis.v3 v3.6.4 - gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect - gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 // indirect gopkg.in/src-d/go-git.v4 v4.11.0 gopkg.in/telegram-bot-api.v4 v4.6.4 - honnef.co/go/tools v0.0.0-20190530170028-a1efa522b896 // indirect + honnef.co/go/tools v0.0.0-20190602125119-5a4a2f4a438d // indirect ) exclude sourcegraph.com/sourcegraph/go-diff v0.5.1 diff --git a/go.sum b/go.sum index 2f2b3d59..1a83d46a 100644 --- a/go.sum +++ b/go.sum @@ -1,181 +1,715 @@ +cloud.google.com/go v0.0.0-20170206221025-ce650573d812/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.23.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.28.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.35.0/go.mod h1:UE4juzxiHpKLbqrOrwVrKuaZvUtLA9CSnaYO+y53jxA= +cloud.google.com/go v0.35.1/go.mod h1:wfjPZNvXCBYESy3fIynybskMP48KVPrjSPCnXiK7Prg= +cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= +contrib.go.opencensus.io/exporter/aws v0.0.0-20180906190126-dd54a7ef511e/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/ocagent v0.4.2/go.mod h1:YuG83h+XWwqWjvCqn7vK4KSyLKhThY3+gNGQ37iS2V0= +contrib.go.opencensus.io/exporter/ocagent v0.4.7/go.mod h1:+KkYrcvvEN0E5ls626sqMv8PdMx2931feKtzIwP01qI= +contrib.go.opencensus.io/exporter/ocagent v0.4.9/go.mod h1:ueLzZcP7LPhPulEBukGn4aLh7Mx9YJwpVJ9nL2FYltw= +contrib.go.opencensus.io/exporter/ocagent v0.4.11/go.mod h1:7ihiYRbdcVfW4m4wlXi9WRPdv79C0fStcjNlyE6ek9s= +contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= +contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= +contrib.go.opencensus.io/exporter/stackdriver v0.6.0/go.mod h1:QeFzMJDAw8TXt5+aRaSuE8l5BwaMIOIlaVkBOPRuMuw= +contrib.go.opencensus.io/exporter/stackdriver v0.9.1/go.mod h1:hNe5qQofPbg6bLQY5wHCvQ7o+2E5P8PkegEuQ+MyRw0= +contrib.go.opencensus.io/exporter/stackdriver v0.9.2/go.mod h1:QUT4zIQm95ItO2Cy7n+V7a3LM9iBvqsLJ3rHKqHUULI= +contrib.go.opencensus.io/exporter/stackdriver v0.10.1/go.mod h1:aX6wdwcO6bNSCDtnHBthppqGDvgg2XCcHb9K8UhckAo= +contrib.go.opencensus.io/exporter/stackdriver v0.11.0/go.mod h1:hA7rlmtavV03FGxzWXAPBUnZeZBhWN/QYQAuMtxc9Bk= +contrib.go.opencensus.io/exporter/stackdriver v0.12.0/go.mod h1:nDPyhzW+GXyxrKsf328j/HLDYHMypbVlKSZOtl8CCDM= +contrib.go.opencensus.io/integrations/ocsql v0.1.2/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= +contrib.go.opencensus.io/integrations/ocsql v0.1.3/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= +contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= +contrib.go.opencensus.io/resource v0.0.0-20190131005048-21591786a5e0/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= +contrib.go.opencensus.io/resource v0.1.0/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/app/changes v0.0.0-20181114035150-5af16e21babb/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/app/changes v0.0.0-20190324224104-4ceb812fd96a/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/app/changes v0.0.0-20190416035755-036c35ce260f/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/service/change v0.0.0-20190203025214-430bf650e55a/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/service/change v0.0.0-20190301072032-c25fb47d71b3/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/service/change v0.0.0-20190327024903-e9885884f070/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/service/change v0.0.0-20190416035443-a1ef85d7455f/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +dmitri.shuralyov.com/state v0.0.0-20190403024436-2cf192113e66/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/99designs/gqlgen v0.7.1/go.mod h1:KSQDfLlTTGmzlRgLGm6HeKKKo598l5E2svEM6Nz2Jnw= +github.com/99designs/gqlgen v0.7.2/go.mod h1:KSQDfLlTTGmzlRgLGm6HeKKKo598l5E2svEM6Nz2Jnw= +github.com/99designs/gqlgen v0.8.3/go.mod h1:aLyJw9xUgdJxZ8EqNQxo2pGFhXXJ/hq8t7J4yn8TgI4= +github.com/99designs/gqlgen v0.9.0/go.mod h1:HrrG7ic9EgLPsULxsZh/Ti+p0HNWgR3XRuvnD0pb5KY= +github.com/Azure/azure-amqp-common-go v1.1.3/go.mod h1:FhZtXirFANw40UXI2ntweO+VOkfaw8s6vZxUiRhLYW8= +github.com/Azure/azure-amqp-common-go v1.1.4/go.mod h1:FhZtXirFANw40UXI2ntweO+VOkfaw8s6vZxUiRhLYW8= +github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-pipeline-go v0.1.9/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-sdk-for-go v16.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v21.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v24.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v26.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v26.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v26.7.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v27.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v27.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v27.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v28.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v30.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-service-bus-go v0.2.0/go.mod h1:auph+otjChRM1T34fA7a3JDAcUeMEPZXs3F21VHJwqI= +github.com/Azure/azure-service-bus-go v0.3.0/go.mod h1:4Z8Dr50cCNhL2bjp3mMbCN+HwuKfmDhMpfKg60StoPw= +github.com/Azure/azure-service-bus-go v0.4.0/go.mod h1:d9ho9e/06euiTwGpKxmlbpPhFUsfCsq6a4tZ68r51qI= +github.com/Azure/azure-service-bus-go v0.4.1/go.mod h1:d9ho9e/06euiTwGpKxmlbpPhFUsfCsq6a4tZ68r51qI= +github.com/Azure/azure-service-bus-go v0.7.0/go.mod h1:OAXCC0XjYAQ0S1zF6ZQC7irHRwtP6ViTg1csWQUFbrU= +github.com/Azure/azure-storage-blob-go v0.0.0-20181023070848-cf01652132cc/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= +github.com/Azure/azure-storage-blob-go v0.0.0-20190104215108-45d0c5e3638e/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= +github.com/Azure/azure-storage-blob-go v0.0.0-20190123011202-457680cc0804/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= +github.com/Azure/azure-storage-blob-go v0.6.0/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v10.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v10.15.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v11.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v11.1.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v11.3.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v11.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v12.1.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v0.0.0-20160329135253-cc2f4770f4d6/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v0.0.0-20190323183505-07c7c350327b/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v0.0.0-20190405105943-4a998718e1d2/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/dd-trace-go v0.6.1/go.mod h1:SmQTTcC37XMyEm75HV0AWiZIYxDiaNhRi49zorIpW+o= +github.com/DataDog/dd-trace-go v1.14.0/go.mod h1:SmQTTcC37XMyEm75HV0AWiZIYxDiaNhRi49zorIpW+o= +github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.4.0/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20181009230506-ac834ce67862/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20181215173202-6f1ecdcf9588/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190312192040-a2a65ffce834/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190405210948-c70a36b8193f/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190418212003-6ac0b49e7197/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190507215335-7265f74d826f/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= +github.com/Jeffail/gabs v1.1.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/Jeffail/gabs v1.2.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig v2.18.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc= github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.4/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= +github.com/RoaringBitmap/roaring v0.4.16/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= +github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= +github.com/SAP/go-hdb v0.12.0/go.mod h1:etBT+FAi1t5k3K3tf5vQTnosgYmhDkRi8jEnQqCnxF0= +github.com/SAP/go-hdb v0.13.1/go.mod h1:etBT+FAi1t5k3K3tf5vQTnosgYmhDkRi8jEnQqCnxF0= +github.com/SAP/go-hdb v0.13.2/go.mod h1:etBT+FAi1t5k3K3tf5vQTnosgYmhDkRi8jEnQqCnxF0= +github.com/SAP/go-hdb v0.14.0/go.mod h1:7fdQLVC2lER3urZLjZCm0AuMQfApof92n3aylBPEkMo= +github.com/SAP/go-hdb v0.14.1/go.mod h1:7fdQLVC2lER3urZLjZCm0AuMQfApof92n3aylBPEkMo= +github.com/SermoDigital/jose v0.0.0-20180104203859-803625baeddc/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= +github.com/SermoDigital/jose v0.9.1/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= +github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.20.1/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.21.0/go.mod h1:yuqtN/pe8cXRWG5zPaO7hCfNJp5MwmkoJEoLjkm5tCQ= +github.com/Shopify/sarama v1.22.1/go.mod h1:FRzlvRpMFO/639zY1SDxUxkqH97Y0ndM5CbGj6oG3As= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/Sirupsen/logrus v1.0.6/go.mod h1:rmk17hk6i8ZSAJkSDa7nOxamrG+SP4P0mm+DAvExv4U= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= +github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes= +github.com/aclements/go-gg v0.0.0-20170323211221-abd1f791f5ee/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes= +github.com/aclements/go-moremath v0.0.0-20161014184102-0ff62e0875ff/go.mod h1:idZL3yvz4kzx1dsBOAC+oYv6L92P1oFEhUXUB1A/lwQ= +github.com/aclements/go-moremath v0.0.0-20180329182055-b1aff36309c7/go.mod h1:idZL3yvz4kzx1dsBOAC+oYv6L92P1oFEhUXUB1A/lwQ= +github.com/aclements/go-moremath v0.0.0-20190506201756-286cc0be6f75/go.mod h1:idZL3yvz4kzx1dsBOAC+oYv6L92P1oFEhUXUB1A/lwQ= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/agnivade/levenshtein v1.0.2/go.mod h1:JLvzGblJATanj48SD0YhHTEFGkWvw3ASLFWSiMIFXsE= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190122153857-e0ace9b64d22/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190315041907-dc6014a0c267/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190328075325-5887fb5d0027/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190329064014-6e358769c32a/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190404024044-fa20eadc7680/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190412020505-60e2075261b6/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190531090255-da3a70fec85f/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= +github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/aliyun/aliyun-oss-go-sdk v1.9.8/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= +github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= +github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s= +github.com/anacrolix/missinggo v0.1.0/go.mod h1:IN+9GUe7OxKMIs/XeXEbT/rMUolmJzmlZiXHS7FwD/Y= +github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= +github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk= +github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= +github.com/anacrolix/tagflag v0.0.0-20180803105420-3a8ff5428f76/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= +github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= +github.com/anacrolix/utp v0.0.0-20180219060659-9e0e1d1d0572/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/apache/arrow/go/arrow v0.0.0-20181031164735-a56c009257a7/go.mod h1:GjvccvtI06FGFvRU1In/maF7tKp3h7GBV9Sexo5rNPM= +github.com/apache/arrow/go/arrow v0.0.0-20181217213538-e9ed591db9cb/go.mod h1:GjvccvtI06FGFvRU1In/maF7tKp3h7GBV9Sexo5rNPM= +github.com/apache/arrow/go/arrow v0.0.0-20190107214733-134081bea48d/go.mod h1:GjvccvtI06FGFvRU1In/maF7tKp3h7GBV9Sexo5rNPM= +github.com/apache/arrow/go/arrow v0.0.0-20190315010344-548e1949d527/go.mod h1:GjvccvtI06FGFvRU1In/maF7tKp3h7GBV9Sexo5rNPM= +github.com/apache/arrow/go/arrow v0.0.0-20190405001043-449530509241/go.mod h1:hSfqYaVcWAXpLRM2R4x/dsonpVt20ACXDXM3Mow8jTg= +github.com/apache/arrow/go/arrow v0.0.0-20190426170622-338c62a2a205/go.mod h1:W8yIftLTH1FLJvxuZc4tFnIlZ2tWg7RCoJR1HcETAso= +github.com/apache/arrow/go/arrow v0.0.0-20190531152918-dbeab70863c4/go.mod h1:NG5SvIQXIxzJR5lGmoXTX9R/EmkArKbPPFu0DUFSz10= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apex/log v1.1.0/go.mod h1:yA770aXIDQrhVOIGurT/pVdfCpSq1GQV/auzMN5fzvY= +github.com/araddon/gou v0.0.0-20190110011759-c797efecbb61/go.mod h1:ikc1XA58M+Rx7SEbf0bLJCfBkwayZ8T5jBo5FXK8Uz8= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asim/go-awsxray v0.0.0-20161209120537-0d8a60b6e205/go.mod h1:frVmN4PtXUuL1EbZn0uL4PHSTKNKFnbMpBIhngqMuNQ= +github.com/asim/go-bson v0.0.0-20160318195205-84522947cabd/go.mod h1:L59ZX7HuzTbNzFBt8g3SJkRraj+GBOgvLAfJYJUcQ5w= +github.com/aws/aws-sdk-go v1.14.17/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k= +github.com/aws/aws-sdk-go v1.15.24/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.31/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.57/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.59/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= +github.com/aws/aws-sdk-go v1.15.64/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= +github.com/aws/aws-sdk-go v1.16.23/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.16.27/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.16.33/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.17.5/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.18.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.18.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.5/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.16/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= +github.com/benbjohnson/tmpl v1.0.0/go.mod h1:igT620JFIi44B6awvU9IsDhR77IXWtFigTLil/RPdps= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/bouk/httprouter v0.0.0-20160817010721-ee8b3818a7f5/go.mod h1:CDReaxg1cmLrtcasZy43l4EYPAknXLiQSrb7tLw5zXM= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= +github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= +github.com/briankassouf/jose v0.9.1/go.mod h1:HQhVmdUf7dBNwIIdBTivnCDxcf6IZY3/zrb+uKSJz6Y= +github.com/briankassouf/jose v0.9.2-0.20180619214549-d2569464773f/go.mod h1:HQhVmdUf7dBNwIIdBTivnCDxcf6IZY3/zrb+uKSJz6Y= github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5awQiY= github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/c-bata/go-prompt v0.2.3/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= +github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= +github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.1.0-0.20181214143942-ba49f56771b8/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.1.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/centrify/cloud-golang-sdk v0.0.0-20180119173102-7c97cc6fde16/go.mod h1:C0rtzmGXgN78pYR0tGJFhtHgkbAs0lIbHwkB81VxDQE= +github.com/centrify/cloud-golang-sdk v0.0.0-20190214225812-119110094d0f/go.mod h1:C0rtzmGXgN78pYR0tGJFhtHgkbAs0lIbHwkB81VxDQE= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chrismalek/oktasdk-go v0.0.0-20181212195951-3430665dfaa0/go.mod h1:5d8DqS60xkj9k3aXfL3+mXBH0DPYO0FQjcKosxl+b/Q= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v0.0.0-20161109192337-d17a8420c36e/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonus-gometrics v2.2.5+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonus-gometrics v2.2.6+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.0.0-20161110002650-365d370cc145/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20180326210544-5e605d46809c/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M= github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= +github.com/coredns/coredns v1.4.0/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= +github.com/coredns/coredns v1.5.0/go.mod h1:He0NCGwdo32s+0TFFuiB9ccnFZRAsjdrW5Q3Vv5Ne6g= +github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.11+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190204112023-081494f7ee4f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/dancannon/gorethink v4.0.0+incompatible/go.mod h1:BLvkat9KmZc1efyYwhz3WnybhRZtgF1K929FD8z1avU= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20180620032804-94c9c97e8c9f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20190313032549-041949b8d268/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20190328043727-2183450503ad/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20190401154936-ce35bd87d4b3/go.mod h1:EcO5fNtMZHCMjAvj8LE6T+5bphSdR6LQ75n+m1TtsFI= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/denverdino/aliyungo v0.0.0-20190325014556-e961d750c214/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/denverdino/aliyungo v0.0.0-20190410085603-611ead8a6fed/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/digitalocean/godo v1.1.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= +github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= +github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= +github.com/digitalocean/godo v1.15.0/go.mod h1:AAPQ+tiM4st79QHlEBTg8LM7JQNre4SAQCbn56wEyKY= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/dnstap/golang-dnstap v0.0.0-20170829151710-2cf77a2b5e11/go.mod h1:s1PfVYYVmTMgCSPtho4LKBDecEHJWtiVDPNv78Z985U= +github.com/dnstap/golang-dnstap v0.1.0/go.mod h1:s1PfVYYVmTMgCSPtho4LKBDecEHJWtiVDPNv78Z985U= +github.com/docker/distribution v2.6.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.0.0-20180422163414-57142e89befe/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16 h1:dmUn0SuGx7unKFwxyeQ/oLUHhEfZosEDrpmYM+6MTuc= github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/duosecurity/duo_api_golang v0.0.0-20181024123116-92fea9203dbc/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= +github.com/duosecurity/duo_api_golang v0.0.0-20190107154727-539434bf0d45/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= +github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.1.1/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.8.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.3+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo= github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.0.0-20180919002855-2137d9196328/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1rk/dqhiCC/4ExuWJZVuk= +github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/estesp/manifest-tool v0.9.0/go.mod h1:w/oandYlJC/m8nkP8UaJVxsm/LwjurJQHXR27njws74= +github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/farsightsec/golang-framestream v0.0.0-20181102145529-8a0cb8ba8710/go.mod h1:eNde4IQyEiA5br02AouhEHCu3p3UzrCdFR4LuQHklMI= +github.com/farsightsec/golang-framestream v0.0.0-20190425193708-fa4b164d59b8/go.mod h1:eNde4IQyEiA5br02AouhEHCu3p3UzrCdFR4LuQHklMI= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v0.0.0-20180123065059-ebf56d35bba7/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c h1:pBgVXWDXju1m8W4lnEeIqTHPOzhTUO81a7yknM/xQR4= github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c/go.mod h1:pFdJbAhRf7rh6YYMUdIQGyzne6zYL1tCUW8QV2B3UfY= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsouza/go-dockerclient v1.4.1 h1:W7wuJ3IB48WYZv/UBk9dCTIb9oX805+L9KIm65HcUYs= github.com/fsouza/go-dockerclient v1.4.1/go.mod h1:PUNHxbowDqRXfRgZqMz1OeGtbWC6VKyZvJ99hDjB0qs= +github.com/fullsailor/pkcs7 v0.0.0-20180613152042-8306686428a5/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/gammazero/deque v0.0.0-20180920172122-f6adf94963e4/go.mod h1:GeIq9qoE43YdGnDXURnmKTnGg15pQz4mYkXSTChbneI= +github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7/go.mod h1:GeIq9qoE43YdGnDXURnmKTnGg15pQz4mYkXSTChbneI= +github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622/go.mod h1:D90+MBHVc9Sk1lJAbEVgws0eYEurY4mv2TDso3Nxh3w= +github.com/gammazero/workerpool v0.0.0-20181230203049-86a96b5d5d92/go.mod h1:w9RqFVO2BM3xwWEcAB8Fwp0OviTBBEiRmSBDfbXnd3w= +github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56/go.mod h1:w9RqFVO2BM3xwWEcAB8Fwp0OviTBBEiRmSBDfbXnd3w= +github.com/gammazero/workerpool v0.0.0-20190521015540-3b91a70bc0a1/go.mod h1:avlwxCMavNtjwf7NrfnzdIGU3OZYI5D1NFQ2Rn3nHKg= github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getkin/kin-openapi v0.1.0/go.mod h1:+0ZtELZf+SlWH8ZdA/IeFb3L/PKOKJx8eGxAlUZ/sOU= +github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.1.4 h1:5N8AYXpaQAPy0L7linKa5aI+WRfyYagAhjksVzxh+mI= github.com/gliderlabs/ssh v0.1.4/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= +github.com/go-acme/lego v2.6.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= +github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ini/ini v1.39.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ini/ini v1.41.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ini/ini v1.42.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-ldap/ldap v2.5.1+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-ldap/ldap v3.0.0+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U= github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-redsync/redsync v1.2.0 h1:a4y3xKQUOA5092Grjps3F5vaRbjA9uoUB59RVwOMttA= github.com/go-redsync/redsync v1.2.0/go.mod h1:QClK/s99KRhfKdpxLTMsI5mSu43iLp0NfOneLPie+78= +github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stomp/stomp v2.0.2+incompatible/go.mod h1:VqCtqNZv1226A1/79yh+rMiFUcfY3R109np+7ke4n0c= +github.com/go-stomp/stomp v2.0.3+incompatible/go.mod h1:VqCtqNZv1226A1/79yh+rMiFUcfY3R109np+7ke4n0c= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= +github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gocql/gocql v0.0.0-20180617115710-e06f8c1bcd78/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/gocql/gocql v0.0.0-20181117210152-33c0e89ca93a/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/gocql/gocql v0.0.0-20190122205811-30de9a1866a8/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/gocql/gocql v0.0.0-20190325140904-fc3925ac2cbd/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/gocql/gocql v0.0.0-20190402132108-0e1d5de854df/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/gocql/gocql v0.0.0-20190523124812-0680bfb96414/go.mod h1:Q7Sru5153KG8D9zwueuQJB3ccJf9/bIwF/x8b3oKgT8= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang/gddo v0.0.0-20181116215533-9bd4a3295021/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= +github.com/golang/gddo v0.0.0-20190312205958-5a2505f3dbf0/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= +github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20180924190550-6f2cf27854a4/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= +github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= +github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= +github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= +github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= +github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.10.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v25 v25.0.1/go.mod h1:6z5pC69qHtrPJ0sXPsj4BLnd82b+r6sLB7qcBoRZqpw= +github.com/google/go-github/v25 v25.1.1/go.mod h1:6z5pC69qHtrPJ0sXPsj4BLnd82b+r6sLB7qcBoRZqpw= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190109223431-e84dfd68c163/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190309163659-77426154d546/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190404155422-f8f10df84213/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v0.0.0-20181012225330-46f0354f6315/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v0.0.0-20171113160352-8c31c18f31ed/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.2.0/go.mod h1:ptBl5bWD3nzmJHVNwYHV3v4wdtKzBMlU2YbtKQCG9GI= +github.com/google/wire v0.2.1/go.mod h1:ptBl5bWD3nzmJHVNwYHV3v4wdtKzBMlU2YbtKQCG9GI= +github.com/google/wire v0.2.2/go.mod h1:7FHVg6mFpFQrjeUZrm+BaD50N5jnDKm50uVPTpyYOmU= +github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= +github.com/gophercloud/gophercloud v0.0.0-20190307220656-fe1ba5ce12dd/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gophercloud/gophercloud v0.0.0-20190328150603-33e54f40ffcf/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gophercloud/gophercloud v0.0.0-20190405143950-35c7fd233bfd/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/goreleaser/goreleaser v0.94.0/go.mod h1:OjbYR2NhOI6AEUWCowMSBzo9nP1aRif3sYtx+rhp+Zo= +github.com/goreleaser/goreleaser v0.103.1/go.mod h1:WCm/LBEy+ZIL1fvKRLPM8t4vStnRHkYWfEu1EwrskD4= +github.com/goreleaser/goreleaser v0.104.1/go.mod h1:YCWszXb4t6HZ7gzeg5TcbPJC2Ad8cFvsknUS0CwS3yY= +github.com/goreleaser/goreleaser v0.108.0/go.mod h1:3fm9v8aIPfhWUfF8UKMW/FxJmb0dNcfxn3KMFRmAHtY= +github.com/goreleaser/nfpm v0.9.7/go.mod h1:F2yzin6cBAL9gb+mSiReuXdsfTrOQwDMsuSpULof+y4= +github.com/goreleaser/nfpm v0.10.0/go.mod h1:F2yzin6cBAL9gb+mSiReuXdsfTrOQwDMsuSpULof+y4= +github.com/goreleaser/nfpm v0.11.0/go.mod h1:F2yzin6cBAL9gb+mSiReuXdsfTrOQwDMsuSpULof+y4= +github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= +github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA= +github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190203031600-7a902570cb17/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.4.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.5.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.7.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.8.3/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.8.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hailocab/go-geoindex v0.0.0-20160127134810-64631bfe9711/go.mod h1:+v2qJ3UZe4q2GfgZO4od004F/cMgJbmPSs7dD/ZMUkY= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/consul v1.4.2/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/consul v1.4.3/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/consul v1.4.4/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/consul v1.5.1 h1:p7tRmQ4m3ZMYkGQkuyjLXKbdU1weeumgZFqZOvw7o4c= +github.com/hashicorp/consul v1.5.1/go.mod h1:QsmgXh2YA9Njv6y3/FHXqHYhsMye++3oBoAZ6SR8R8I= github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.0/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= +github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-discover v0.0.0-20190319153616-61771d82ff54/go.mod h1:CImRKBstQOB5m5fNKXFT/CCp5WyWCLvcYydd0kb2EaA= +github.com/hashicorp/go-discover v0.0.0-20190403160810-22221edb15cd/go.mod h1:ueUgD9BeIocT7QNuvxSyJyPAM9dfifBcaWmeybb67OY= +github.com/hashicorp/go-discover v0.0.0-20190522154730-8aba54d36e17/go.mod h1:FTV98wIi2RF5iDl1iLR/cB+no+B//ODP6133EcC9djw= +github.com/hashicorp/go-gcp-common v0.0.0-20180425173946-763e39302965/go.mod h1:LNbios2fdMAuLA1dsYUvUcoCYIfywcCEK8/ooaWjoOA= +github.com/hashicorp/go-gcp-common v0.5.0/go.mod h1:IDGUI2N/OS3PiU4qZcXJeWKPI6O/9Y8hOrbSiMcqyYw= +github.com/hashicorp/go-hclog v0.0.0-20180402200405-69ff559dc25f/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.0.0-20190109152822-4783caec6f2e/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v0.0.0-20180223233045-1289e7fffe71/go.mod h1:kbfItVoBJwCfKXDXN4YoAXjxcFVZ7MRrJzyTX6H4giE= +github.com/hashicorp/go-memdb v0.0.0-20181108192425-032f93b25bec/go.mod h1:kbfItVoBJwCfKXDXN4YoAXjxcFVZ7MRrJzyTX6H4giE= +github.com/hashicorp/go-memdb v0.0.0-20190306140544-eea0b16292ad/go.mod h1:kbfItVoBJwCfKXDXN4YoAXjxcFVZ7MRrJzyTX6H4giE= +github.com/hashicorp/go-memdb v1.0.0/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M= +github.com/hashicorp/go-memdb v1.0.3/go.mod h1:LWQ8R70vPrS4OEY9k28D2z8/Zzyu34NVzeRibGAzHO0= +github.com/hashicorp/go-msgpack v0.0.0-20150518234257-fa3f63826f7c/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v0.0.0-20180331002553-e8d22c780116/go.mod h1:JSqWYsict+jzcj0+xElxyrBQRPNoiWQuddnxArJ7XHQ= +github.com/hashicorp/go-plugin v0.0.0-20181030172320-54b6ff97d818/go.mod h1:Ft7ju2vWzhO0ETMKUVo12XmXmII6eSUS4rsPTkY/siA= +github.com/hashicorp/go-plugin v0.0.0-20181212150838-f444068e8f5a/go.mod h1:Ft7ju2vWzhO0ETMKUVo12XmXmII6eSUS4rsPTkY/siA= +github.com/hashicorp/go-plugin v0.0.0-20190220160451-3f118e8ee104/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.0.0-20180531211321-3b087ef2d313/go.mod h1:fXcdFsQoipQa7mwORhKad5jmDCeSy/RCGzWA08PO0lM= +github.com/hashicorp/go-retryablehttp v0.5.0/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.5.1/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg= github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.1/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= @@ -183,49 +717,192 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v0.0.0-20170202080759-03c5bf6be031/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v0.0.0-20180906183839-65a6292f0157/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= +github.com/hashicorp/hil v0.0.0-20190212132231-97b3a9cdfa93/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE= github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs= github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= +github.com/hashicorp/nomad v0.8.7/go.mod h1:WRaKjdO1G2iqi86TvTjIYtKTyxg4pl7NLr9InxtWaI0= +github.com/hashicorp/nomad v0.9.1/go.mod h1:WRaKjdO1G2iqi86TvTjIYtKTyxg4pl7NLr9InxtWaI0= +github.com/hashicorp/raft v1.0.0/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI= +github.com/hashicorp/raft v1.0.1-0.20190409200437-d9fe23f7d472/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI= +github.com/hashicorp/raft v1.1.0 h1:qPMePEczgbkiQsqCsRfuHRqvDUO+zmAInDaD5ptXlq0= +github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= +github.com/hashicorp/raft-boltdb v0.0.0-20150201200839-d1e82c1ec3f1/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= +github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= +github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.3 h1:MWYcmct5EtKz0efYooPcL0yNkem+7kWxqXDi/UIh+8k= github.com/hashicorp/serf v0.8.3/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k= +github.com/hashicorp/vault v0.10.3/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= +github.com/hashicorp/vault v0.11.5/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= +github.com/hashicorp/vault v1.0.2/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= +github.com/hashicorp/vault v1.0.3/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= +github.com/hashicorp/vault v1.1.0/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= +github.com/hashicorp/vault v1.1.2/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= +github.com/hashicorp/vault-plugin-auth-alicloud v0.0.0-20181109180636-f278a59ca3e8/go.mod h1:o3i5QQWgV5+SYouIn++L9D0kbhLYB3FjxNRHNf6KS+Q= +github.com/hashicorp/vault-plugin-auth-alicloud v0.0.0-20190311155555-98628998247d/go.mod h1:o3i5QQWgV5+SYouIn++L9D0kbhLYB3FjxNRHNf6KS+Q= +github.com/hashicorp/vault-plugin-auth-alicloud v0.0.0-20190320211238-36e70c54375f/go.mod h1:o3i5QQWgV5+SYouIn++L9D0kbhLYB3FjxNRHNf6KS+Q= +github.com/hashicorp/vault-plugin-auth-alicloud v0.5.1/go.mod h1:v0d6/ft2ESFHG/PB2pqcwDPlvtAWWfOmfsY0nfbIMy0= +github.com/hashicorp/vault-plugin-auth-azure v0.0.0-20181207232528-4c0b46069a22/go.mod h1:f+VmjSQIxxO+YTeO3FbPWRPCPbd3f3lwpP6jaO/YduQ= +github.com/hashicorp/vault-plugin-auth-azure v0.0.0-20190201222632-0af1d040b5b3/go.mod h1:f+VmjSQIxxO+YTeO3FbPWRPCPbd3f3lwpP6jaO/YduQ= +github.com/hashicorp/vault-plugin-auth-azure v0.0.0-20190320211138-f34b96803f04/go.mod h1:f+VmjSQIxxO+YTeO3FbPWRPCPbd3f3lwpP6jaO/YduQ= +github.com/hashicorp/vault-plugin-auth-azure v0.5.1/go.mod h1:D/slkpcqcZMqslj1X9jfU9aIOrC41LVkfDQ9lFhYg0o= +github.com/hashicorp/vault-plugin-auth-centrify v0.0.0-20180816201131-66b0a34a58bf/go.mod h1:IIz+CMBKBEFyjeBeFUlpoUuMOyFb7mybOUNP6GX1xuk= +github.com/hashicorp/vault-plugin-auth-centrify v0.0.0-20190320211357-44eb061bdfd8/go.mod h1:IIz+CMBKBEFyjeBeFUlpoUuMOyFb7mybOUNP6GX1xuk= +github.com/hashicorp/vault-plugin-auth-centrify v0.5.1/go.mod h1:GHplZPj7NfPWdeCkgTRnNzbjVP5IW5MNm7+MMsjobpQ= +github.com/hashicorp/vault-plugin-auth-gcp v0.0.0-20181210200133-4d63bbfe6fcf/go.mod h1:E/E+5CuQCjOn/YGCmZ/tA7GwLey/lN1PwwJOOa9Iqy0= +github.com/hashicorp/vault-plugin-auth-gcp v0.0.0-20190201215414-7d4c2101e7d0/go.mod h1:E/E+5CuQCjOn/YGCmZ/tA7GwLey/lN1PwwJOOa9Iqy0= +github.com/hashicorp/vault-plugin-auth-gcp v0.0.0-20190320214413-e8308b5e41c9/go.mod h1:E/E+5CuQCjOn/YGCmZ/tA7GwLey/lN1PwwJOOa9Iqy0= +github.com/hashicorp/vault-plugin-auth-gcp v0.0.0-20190402000036-441a7965e9fe/go.mod h1:E/E+5CuQCjOn/YGCmZ/tA7GwLey/lN1PwwJOOa9Iqy0= +github.com/hashicorp/vault-plugin-auth-gcp v0.5.1/go.mod h1:eLj92eX8MPI4vY1jaazVLF2sVbSAJ3LRHLRhF/pUmlI= +github.com/hashicorp/vault-plugin-auth-jwt v0.0.0-20190117220024-3e8048f1026f/go.mod h1:j6Xmkj3dzuC63mivquwVVTlxjwDndwNxi4cJUku40J8= +github.com/hashicorp/vault-plugin-auth-jwt v0.0.0-20190314211503-86b44673ce1e/go.mod h1:j6Xmkj3dzuC63mivquwVVTlxjwDndwNxi4cJUku40J8= +github.com/hashicorp/vault-plugin-auth-jwt v0.0.0-20190321042813-9474f90fb1df/go.mod h1:j6Xmkj3dzuC63mivquwVVTlxjwDndwNxi4cJUku40J8= +github.com/hashicorp/vault-plugin-auth-jwt v0.0.0-20190405223429-c05fb7def42b/go.mod h1:j6Xmkj3dzuC63mivquwVVTlxjwDndwNxi4cJUku40J8= +github.com/hashicorp/vault-plugin-auth-jwt v0.5.1/go.mod h1:5VU7gc6/BEEFQW/viqMs3LBxI1D1cxJmKqKQEP3JUP4= +github.com/hashicorp/vault-plugin-auth-kubernetes v0.0.0-20181130162533-091d9e5d5fab/go.mod h1:PqRUU5TaQ6FwVTsHPLrJs1F+T5IjbSzlfTy9cTyGeHM= +github.com/hashicorp/vault-plugin-auth-kubernetes v0.0.0-20190201222209-db96aa4ab438/go.mod h1:PqRUU5TaQ6FwVTsHPLrJs1F+T5IjbSzlfTy9cTyGeHM= +github.com/hashicorp/vault-plugin-auth-kubernetes v0.0.0-20190328163920-79931ee7aad5/go.mod h1:PqRUU5TaQ6FwVTsHPLrJs1F+T5IjbSzlfTy9cTyGeHM= +github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.1/go.mod h1:qCDsm0njdfUrnN5sFKMLjxGjZKjQf2qB6dReQ4gr4YI= +github.com/hashicorp/vault-plugin-secrets-ad v0.0.0-20181109182834-540c0b6f1f11/go.mod h1:4vRQzvp3JI+g4oUqzcklIEj2UKyhQnpIo+BDbh2uzYM= +github.com/hashicorp/vault-plugin-secrets-ad v0.0.0-20190131222416-4796d9980125/go.mod h1:4vRQzvp3JI+g4oUqzcklIEj2UKyhQnpIo+BDbh2uzYM= +github.com/hashicorp/vault-plugin-secrets-ad v0.0.0-20190327182327-ed2c3d4c3d95/go.mod h1:4vRQzvp3JI+g4oUqzcklIEj2UKyhQnpIo+BDbh2uzYM= +github.com/hashicorp/vault-plugin-secrets-ad v0.5.1/go.mod h1:EH9CI8+0aWRBz8eIgGth0QjttmHWlGvn+8ZmX/ZUetE= +github.com/hashicorp/vault-plugin-secrets-alicloud v0.0.0-20181109181453-2aee79cc5cbf/go.mod h1:rl8WzY7++fZMLXd6Z/k/o9wUmMbOqpTLhbtKs1loMU0= +github.com/hashicorp/vault-plugin-secrets-alicloud v0.0.0-20190131211812-b0abe36195cb/go.mod h1:rl8WzY7++fZMLXd6Z/k/o9wUmMbOqpTLhbtKs1loMU0= +github.com/hashicorp/vault-plugin-secrets-alicloud v0.0.0-20190320213517-3307bdf683cb/go.mod h1:rl8WzY7++fZMLXd6Z/k/o9wUmMbOqpTLhbtKs1loMU0= +github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.1/go.mod h1:MspbyD2pPrYgBnYIawkBsFinaDb9lx9PA6uBYOG+d8I= +github.com/hashicorp/vault-plugin-secrets-azure v0.0.0-20181207232500-0087bdef705a/go.mod h1:/DhLpYuRP2o00gkj6S0Gy7NvKk5AaAtP6p3f+OmxDUI= +github.com/hashicorp/vault-plugin-secrets-azure v0.0.0-20190320211922-2dc8a8a5e490/go.mod h1:/DhLpYuRP2o00gkj6S0Gy7NvKk5AaAtP6p3f+OmxDUI= +github.com/hashicorp/vault-plugin-secrets-azure v0.5.1/go.mod h1:9D3lbhWkN7kTCIrQl8yxMU4IkisAY3SYZaRvseih6ZE= +github.com/hashicorp/vault-plugin-secrets-gcp v0.0.0-20180921173200-d6445459e80c/go.mod h1:IV2OZZZ9FCtSYeKDLsnO5JipMdjwachVISz9pNuQjhs= +github.com/hashicorp/vault-plugin-secrets-gcp v0.0.0-20190311200649-621231cb86fe/go.mod h1:IV2OZZZ9FCtSYeKDLsnO5JipMdjwachVISz9pNuQjhs= +github.com/hashicorp/vault-plugin-secrets-gcp v0.0.0-20190320211452-71903323ecb4/go.mod h1:IV2OZZZ9FCtSYeKDLsnO5JipMdjwachVISz9pNuQjhs= +github.com/hashicorp/vault-plugin-secrets-gcp v0.5.2/go.mod h1:2VjVlKHTwqvcVCkZBhYks+HASDzQ4/bIsJoOpO2YJFY= +github.com/hashicorp/vault-plugin-secrets-gcpkms v0.0.0-20190116164938-d6b25b0b4a39/go.mod h1:2n62quNV4DvfMY5Lxx82NJmx+9pYtv4RltLIFKxEO4E= +github.com/hashicorp/vault-plugin-secrets-gcpkms v0.0.0-20190320213325-9e326a9e802d/go.mod h1:2n62quNV4DvfMY5Lxx82NJmx+9pYtv4RltLIFKxEO4E= +github.com/hashicorp/vault-plugin-secrets-gcpkms v0.5.1/go.mod h1:seBkt6x33ZT20koMcUwV/viMomnXDipsLgK5KUKz2ik= +github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20181106190520-2236f141171e/go.mod h1:VJHHT2SC1tAPrfENQeBhLlb5FbZoKZM+oC/ROmEftz0= +github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20190115203747-edbfe287c5d9/go.mod h1:VJHHT2SC1tAPrfENQeBhLlb5FbZoKZM+oC/ROmEftz0= +github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20190227052836-76a82948fe5b/go.mod h1:VJHHT2SC1tAPrfENQeBhLlb5FbZoKZM+oC/ROmEftz0= +github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20190318174639-195e0e9d07f1/go.mod h1:VJHHT2SC1tAPrfENQeBhLlb5FbZoKZM+oC/ROmEftz0= +github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20190320211621-3ccc8684cf25/go.mod h1:VJHHT2SC1tAPrfENQeBhLlb5FbZoKZM+oC/ROmEftz0= +github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20190404212640-4807e6564154/go.mod h1:VJHHT2SC1tAPrfENQeBhLlb5FbZoKZM+oC/ROmEftz0= +github.com/hashicorp/vault-plugin-secrets-kv v0.5.1/go.mod h1:PIjaafaRr2QlkGl2SNhIywxlejeW0iMUtmx8u9u/a6c= +github.com/hashicorp/vault/api v1.0.1/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE= +github.com/hashicorp/vault/api v1.0.2/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE= +github.com/hashicorp/vault/sdk v0.1.8/go.mod h1:tHZfc6St71twLizWNHvnnbiGFo1aq0eD2jGPLtP8kAU= +github.com/hashicorp/vault/sdk v0.1.11/go.mod h1:XF2Bod+ahPWGARnyFq5LfkOZwWwvveR5ptYwJLqK0ZI= +github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/hudl/fargo v1.2.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd h1:anPrsicrIi2ColgWTVPk+TrN42hJIWlfPHSBP9S0ZkM= github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd/go.mod h1:3LVOLeyx9XVvwPgrt2be44XgSqndprz1G18rSk8KD84= +github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.13.0/go.mod h1:81jeDcHVn1rN5uj9aQ81S72Q8ol8If7N0zM0G8TnxTE= +github.com/influxdata/flux v0.21.4/go.mod h1:0f5Yrm4VPSd/Ne6jIVOVtPo0MFe6jpLCr6vdaZYp7wY= +github.com/influxdata/flux v0.24.0/go.mod h1:0f5Yrm4VPSd/Ne6jIVOVtPo0MFe6jpLCr6vdaZYp7wY= +github.com/influxdata/flux v0.31.1/go.mod h1:nLOr2DG8S2ArvCJKktL3P7s1cBgCQrlH8is1u+k1sR0= +github.com/influxdata/influxdb v1.7.3/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb v1.7.4/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb v1.7.5/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb v1.7.6/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxql v0.0.0-20180925231337-1cbfca8e56b6/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo= +github.com/influxdata/influxql v1.0.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/line-protocol v0.0.0-20190220025226-a3afd890113f/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/line-protocol v0.0.0-20190509173118-5712a8124a9a/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/platform v0.0.0-20190117200541-d500d3cf5589/go.mod h1:YVhys+JOY4wmXtJvdtkzLhS2K/r/px/vPc+EAddK+pg= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= +github.com/jarcoal/httpmock v1.0.1/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jeffchao/backoff v0.0.0-20140404060208-9d7fd7aa17f2/go.mod h1:xkfESuHriIekR+4RoV+fu91j/CfnYM29Zi2tMFw5iD4= +github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee/go.mod h1:N0t2vlmpe8nyZB5ouIbJQPDSR+mH6oe7xHB9VZHSUzM= +github.com/jefferai/jsonx v1.0.0/go.mod h1:OGmqmi2tTeI/PS+qQfBDToLHHJIy/RMp24fPo8vFvoQ= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/joncalhoun/qson v0.0.0-20170526102502-8a9cab3a62b1 h1:lnrOS18wZBYrzdDmnUeg1OVk+kQ3rxG8mZWU89DpMIA= +github.com/joncalhoun/qson v0.0.0-20170526102502-8a9cab3a62b1/go.mod h1:DFXrEwSRX0p/aSvxE21319menCBFeQO0jXpRj7LEZUA= +github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= +github.com/joyent/triton-go v0.0.0-20190112182421-51ffac552869/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kamilsk/retry/v4 v4.0.0/go.mod h1:0af33qDvzbhQqdOBi7iOjEpmP4brbPmNZpo7chYlgcc= +github.com/kamilsk/retry/v4 v4.0.2/go.mod h1:resXTqZ5IUwMFSW/JG2YJ6vp3nf1cX+i6vpjMTZEKXg= +github.com/kamilsk/retry/v4 v4.0.3/go.mod h1:0af33qDvzbhQqdOBi7iOjEpmP4brbPmNZpo7chYlgcc= +github.com/kamilsk/retry/v4 v4.1.0/go.mod h1:7Be8o6Y0GLE/BBg0P3+9jA/WLJsY5QovbT5gHS0+xe4= +github.com/kevinburke/go-bindata v3.11.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= +github.com/kevinburke/go-bindata v3.13.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/keybase/go-crypto v0.0.0-20181031135447-f919bfda4fc1/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/keybase/go-crypto v0.0.0-20181127160227-255a5089e85a/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/keybase/go-crypto v0.0.0-20190312101036-b475f2ecc1fe/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/keybase/go-crypto v0.0.0-20190403132359-d65b6b94177f/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/keybase/go-crypto v0.0.0-20190416182011-b785b22cc757/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= @@ -233,298 +910,932 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/lib/pq v0.0.0-20180523175426-90697d60dd84/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= +github.com/linode/linodego v0.9.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA= +github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04= +github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk= +github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao= +github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= +github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU= github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0= +github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg= github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 h1:MNApn+Z+fIT4NPZopPfCc1obT6aY3SVM6DOctz1A9ZU= github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg= +github.com/lyft/protoc-gen-validate v0.0.0-20180911180927-64fcb82c878e/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/lyft/protoc-gen-validate v0.0.14/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= +github.com/mattbaird/elastigo v0.0.0-20170123220020-2fe47fd29e4b/go.mod h1:5MWrJXKRQyhQdUCF+vu6U5c4nQpg70vW3eHaU0/AYbU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v0.0.0-20161215041557-2d44decb4941/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/mattn/go-tty v0.0.0-20181127064339-e4f871175a2f/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/mattn/go-tty v0.0.0-20190402035014-76a2065f1a95/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/mattn/go-zglob v0.0.0-20171230104132-4959821b4817/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mholt/caddy v0.11.5/go.mod h1:Wb1PlT4DAYSqOEd03MsqkdkXnTxA8v9pKjdpxbqM1kY= +github.com/mholt/caddy v1.0.0/go.mod h1:PzUpQ3yGCTuEuy0KSxEeB4TZOi3zBZ8BR/zY0RBP414= +github.com/mholt/certmagic v0.5.0/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= +github.com/mholt/certmagic v0.5.1/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= +github.com/michaelklishin/rabbit-hole v1.4.0/go.mod h1:vvI1uOitYZi0O5HEGXhaWC1XT80Gy+HvFheJ+5Krlhk= +github.com/michaelklishin/rabbit-hole v1.5.0/go.mod h1:vvI1uOitYZi0O5HEGXhaWC1XT80Gy+HvFheJ+5Krlhk= +github.com/micro/cli v0.0.0-20181223203424-1b0c9793c300/go.mod h1:x9x6qy+tXv17jzYWQup462+j3SIUgDa6vVTzU4IXy/w= +github.com/micro/cli v0.1.0/go.mod h1:jRT9gmfVKWSS6pkKcXQ8YhUyj6bzwxK8Fp5b0Y7qNnk= github.com/micro/cli v0.2.0 h1:ut3rV5JWqZjsXIa2MvGF+qMUP8DAUTvHX9Br5gO4afA= github.com/micro/cli v0.2.0/go.mod h1:jRT9gmfVKWSS6pkKcXQ8YhUyj6bzwxK8Fp5b0Y7qNnk= +github.com/micro/examples v0.0.0-20190130075939-4c88706279b7/go.mod h1:kAtxC8sjgEmhvistg+9GLxPiI/80SYSAMXObHcs1CFw= +github.com/micro/examples v0.1.0 h1:tW/g/rqLy/K/M2ZTvRLPjG3BHW0dlF9TqQ2MtJns5/E= +github.com/micro/examples v0.1.0/go.mod h1:ye1KFooRPX3Iudk0QP+E4Yx3K855v1SqEn1a/G5Z+WY= +github.com/micro/go-api v0.5.0/go.mod h1:itWuEfGqJNM5q5hct5LWYFSxwPZWyngnrRpgMQJxlA0= +github.com/micro/go-api v0.6.0/go.mod h1:GR6fcdDJU0ltEcqyk+5GvKOzfYsGMLes2s58XfwKCVM= +github.com/micro/go-api v0.7.0/go.mod h1:8PL/Y8+JbNShOBaFWRXUPjEC7jriT/ctXgP+hQ6WmUQ= +github.com/micro/go-bot v0.1.0/go.mod h1:wodQnZJedhANtriMfhCJa07WK6UnZE2QFNNRlmL3NoI= +github.com/micro/go-bot v1.0.0/go.mod h1:wodQnZJedhANtriMfhCJa07WK6UnZE2QFNNRlmL3NoI= +github.com/micro/go-bot v1.1.0/go.mod h1:wodQnZJedhANtriMfhCJa07WK6UnZE2QFNNRlmL3NoI= +github.com/micro/go-config v0.13.3/go.mod h1:fVecLls1kW+EJsrlkJYqUmVoJa1epSHhsPMDXppELx0= +github.com/micro/go-config v0.14.0/go.mod h1:ijvLjvxxfDdlH5vO36ZBrzMVGpiGysuwFotX9F84cag= +github.com/micro/go-config v1.1.0/go.mod h1:CXPn1mOHB6dBgqWzmsww9cb267dWThPzppMl93jLQ0I= +github.com/micro/go-grpc v0.9.0/go.mod h1:0oVU9jFs55Pcu4J5+ZI9c4MKgQBm0UWWSiPZyUdQPJo= +github.com/micro/go-grpc v1.0.0/go.mod h1:G5jb/FchX5B0fSZc417Y+dXFsDjtGStsQOgIG7eQcj8= +github.com/micro/go-grpc v1.0.1/go.mod h1:GyRoEEtNJeggw7i7dM10oB1m4exHYHoC2zJruad3Apc= +github.com/micro/go-log v0.1.0 h1:szYSR+yyTsomZM2jyinJC5562DlqffSjHmTZFaeZ2vY= +github.com/micro/go-log v0.1.0/go.mod h1:qaFBF6d6Jk01Gz4cbMkCA2vVuLk3FSaLLjmEGrMCreA= +github.com/micro/go-micro v0.23.0/go.mod h1:3z3lfMkNU9Sr1L/CxL++8pVJmQapRo0N6kNjwYDtOVs= +github.com/micro/go-micro v0.24.0/go.mod h1:G/2AWGXaoz2RoiT8xNNzE9Jn46MAI3GiRvSw6QsCDwI= +github.com/micro/go-micro v0.24.1/go.mod h1:G/2AWGXaoz2RoiT8xNNzE9Jn46MAI3GiRvSw6QsCDwI= +github.com/micro/go-micro v0.25.0/go.mod h1:G/2AWGXaoz2RoiT8xNNzE9Jn46MAI3GiRvSw6QsCDwI= +github.com/micro/go-micro v0.26.0/go.mod h1:CweCFO/pq8dCSIOdzVZ4ooIpUrKlyJ0AcFB269M7PgU= +github.com/micro/go-micro v0.26.1/go.mod h1:Jgc5gPEmDiG1TWE5Qnzzx5qyXnU9VTXKT1FkXkfvt8g= +github.com/micro/go-micro v0.27.0/go.mod h1:DnwIb4pb7podyAgAF8HYMlDkV5sjtcIj9wW4Iq+JyIk= +github.com/micro/go-micro v1.0.0/go.mod h1:DnwIb4pb7podyAgAF8HYMlDkV5sjtcIj9wW4Iq+JyIk= +github.com/micro/go-micro v1.1.0/go.mod h1:DcjZ4Gqfq3WQbWaRJOorwSzmEdvV/sSW9lYpM5m7QgQ= +github.com/micro/go-micro v1.3.0/go.mod h1:t0wGHXCXKUo6N21WYWtUViY7hzxCNNpdR2UhoSYP6qA= +github.com/micro/go-plugins v0.21.1/go.mod h1:dNzFjp55RfRhGm7zC6tPPzbdnAPo8+Yq4DbgEWdx6gc= +github.com/micro/go-plugins v0.22.0/go.mod h1:CJxs9uhwLs73qy1/UryI1kzPDi3Kf1BB5WPGJeNW7+8= +github.com/micro/go-plugins v0.24.1/go.mod h1:D3aeqkF6t2bIc4W7QPAs/g4c3fJXV+V7KFXT6TOP47o= +github.com/micro/go-plugins v0.25.0/go.mod h1:lhhaS9cXFGvwEi9OicAUPOPdFOoYx6jNNiEUZ1gQDMI= +github.com/micro/go-plugins v1.1.0 h1:w063kuPtDen1MZ9dpTKCKd5okNRQzUnJtoqiRP+08Yg= +github.com/micro/go-plugins v1.1.0/go.mod h1:pi1MnFFYfRYMOoU5v+CRO0r+BZkx2sFD794Q80BR51c= +github.com/micro/go-proxy v0.1.0/go.mod h1:atFueQm7iDQY1JBJsIV4vbnN/Rqdge6zE6AjZMqvXgk= +github.com/micro/go-proxy v0.2.0/go.mod h1:LbNKjmquF24GtgaxClke35LHaCqEw1cgrPZ2OPoxKlk= +github.com/micro/go-rcache v0.1.0/go.mod h1:INzyZjXO5M+PmN2A33YxD4TaOY61xjFIM4CfSHv+At8= +github.com/micro/go-rcache v0.2.0/go.mod h1:EoiTwbY2ubQ6lc3ScV+SnmKbelDzeFezDxPDvF8XDxw= +github.com/micro/go-rcache v0.2.1/go.mod h1:aPCNY3RbjBdyd6ShLENl4MDSgpAiWIU4LyNLE9+TOEo= +github.com/micro/go-rcache v0.3.0/go.mod h1:M5zEP6UW8YfsaHHyFdcgTzgIshTPNqMKw38Mwc+ht4g= +github.com/micro/go-web v0.6.0/go.mod h1:d7+RTjvq5mvsIq55hq1/84kbYBbvJKrcUcBxBbQR5Nc= +github.com/micro/go-web v1.0.0/go.mod h1:d7+RTjvq5mvsIq55hq1/84kbYBbvJKrcUcBxBbQR5Nc= +github.com/micro/h2c v1.0.0/go.mod h1:54sOOQW/GRlHhH43vKwOhUb+kHaXhVxR0d3CJhn9alE= +github.com/micro/hipchat v0.0.0-20160328000638-4c67119ac956/go.mod h1:9LPnmAqs2JarMBCqn4eUNkATVCsGQFphxNoQEi28uLU= +github.com/micro/mdns v0.0.0-20181201230301-9c3770d4057a/go.mod h1:SQG6o/94RinohLuB5noHSevg2Iqg2wXLDUn4lj2LWWo= github.com/micro/mdns v0.1.0 h1:fuLybUsfynbigJmCot/54i+gwe0hpc/vtCMvWt2WfDI= github.com/micro/mdns v0.1.0/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc= +github.com/micro/micro v0.20.0/go.mod h1:fkfl/b2vWYkKiUgYW/3uw8OVZTxE/ARw761basyjcBY= +github.com/micro/micro v0.22.0/go.mod h1:P9n/z0/T/F0h3ywgKACNaRlXQitOQtGrYVfhEzmIvw0= +github.com/micro/micro v0.22.1/go.mod h1:P9n/z0/T/F0h3ywgKACNaRlXQitOQtGrYVfhEzmIvw0= +github.com/micro/micro v0.23.2/go.mod h1:Fxh8P04RkEdpsiSzlwqVJR7+OHR1/7vo2obwCCQnwAc= +github.com/micro/micro v1.1.1/go.mod h1:r9wLAx67Dn/ClxSiF4nUt/d1SaZDwPm06hegO6spy0I= +github.com/micro/micro v1.3.0 h1:tcAGRMlOLT7pXlerqrcqR5WaG61MsMUp85oC/3hGBgQ= +github.com/micro/micro v1.3.0/go.mod h1:4G4OPYMb6F+u22H3OuOZalC0EEnXj8g6coA9N7OPRHw= +github.com/micro/util v0.1.0/go.mod h1:MZgOs0nwxzv9k4xQo4fpF9IwZGF2O96F5/phP9X4/Sw= +github.com/micro/util v0.2.0 h1:6u0cPj1TeixEk5cAR9jbcVRUWDQsmCaZvDBiM3zFZuA= +github.com/micro/util v0.2.0/go.mod h1:SgRDkxJJluC2ZNiPfINY42ObEaCAFjL3jP5a+u+qRLU= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.13 h1:x7DQtkU0cedzeS8TD36tT/w1Hm4rDtfCaYYAHE7TTBI= github.com/miekg/dns v1.1.13/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/minio/highwayhash v0.0.0-20190112144901-fc990dfafa15/go.mod h1:NL8wme5P5MoscwAkXfGroz3VgpCdhBw3KYOu5mEsvpU= +github.com/minio/highwayhash v0.0.0-20190131021015-02ca4b43caa3/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= +github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v0.0.0-20160804032330-cdac8253d00f/go.mod h1:eOsF2yLPlBBJPvD+nhl5QMTBSOBbOph6N7j/IDUw7PY= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/gox v1.0.0/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= +github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/pointerstructure v0.0.0-20190323210102-2db4bb651397/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= +github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mna/pigeon v1.0.1-0.20180808201053-bb0192cfc2ae/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20190414153302-2ae31c8b6b30/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nats-io/gnatsd v1.3.0/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ= github.com/nats-io/gnatsd v1.4.1 h1:RconcfDeWpKCD6QIIwiVFcvForlXpWeJP7i5/lDLy44= github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ= +github.com/nats-io/go-nats v1.6.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= +github.com/nats-io/go-nats v1.7.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= github.com/nats-io/go-nats v1.7.2 h1:cJujlwCYR8iMz5ofZSD/p2WLW8FabhkQ2lIEVbSvNSA= github.com/nats-io/go-nats v1.7.2/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= +github.com/nats-io/go-nats-streaming v0.4.0/go.mod h1:gfq4R3c9sKAINOpelo0gn/b9QDMBZnmrttcsNF+lqyo= +github.com/nats-io/go-nats-streaming v0.4.2/go.mod h1:gfq4R3c9sKAINOpelo0gn/b9QDMBZnmrttcsNF+lqyo= +github.com/nats-io/go-nats-streaming v0.4.5/go.mod h1:gfq4R3c9sKAINOpelo0gn/b9QDMBZnmrttcsNF+lqyo= +github.com/nats-io/nats-streaming-server v0.11.2/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c= +github.com/nats-io/nats-streaming-server v0.12.0/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c= +github.com/nats-io/nats-streaming-server v0.12.2/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c= +github.com/nats-io/nats-streaming-server v0.14.2/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c= github.com/nats-io/nats.go v1.7.2 h1:rUV2n05Quwp0dJsnKyaL/KMBb8b50h8dBpQPaxQhtQE= github.com/nats-io/nats.go v1.7.2/go.mod h1:yo+8b7YsyprMCRao9okCBtz4Gfr9nSmu5vdOjuV27BE= github.com/nats-io/nkeys v0.0.2 h1:+qM7QpgXnvDDixitZtQUBDY9w/s9mu1ghS+JIbsrx6M= github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= +github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN+Ito= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/oklog/run v0.0.0-20180308005104-6934b124db28/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.5/go.mod h1:8NDCjKHoHW1XOp/vf3lClHem0b91r4433B67KXyKXAQ= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go-opentracing v0.3.4/go.mod h1:js2AbwmHW0YD9DwIw2JhQWmbfFi/UnWyYwdVhqbCDOE= +github.com/openzipkin/zipkin-go-opentracing v0.3.5/go.mod h1:js2AbwmHW0YD9DwIw2JhQWmbfFi/UnWyYwdVhqbCDOE= +github.com/ory-am/common v0.4.0/go.mod h1:oCYGuwwM8FyYMKqh9vrhBaeUoyz/edx0bgJN6uS6/+k= +github.com/ory/dockertest v3.3.2+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v0.0.0-20180527043350-9f6ff22cfff8/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/pquerna/otp v1.1.0/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk= +github.com/prometheus/client_golang v0.0.0-20171201122222-661e31bf844d/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.0.0-20180328130430-f504d69affe1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180326160409-38c53a9f4bfc/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181218105931-67670fe90761/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20180408092902-8b1c2da0d56d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190203183350-488faf799f86/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190306233201-d0f344d83b0c/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190328153300-af7bedc223fb/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.1 h1:Vb1OE5ZDNKF3yhna6/G+5pHqADNm4I8hUoHj7YQhbZk= github.com/prometheus/procfs v0.0.1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.8.0/go.mod h1:fSI0j+IUQrDd7+ZtR9WKIGtoYAYAJUKcKhYLG25tN4g= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20190512091148-babf20351dd7/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.0.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= +github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.2/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.4/go.mod h1:MyX8oKJCSypBXY66FgANfFbqN8aFXAGoLlnR3eKCzoU= +github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/events v0.0.0-20190403073608-99a35243dbf4/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go v0.0.0-20190121191506-3fef8c783dec/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go v0.0.0-20190330031554-6713ea532688/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gofontwoff v0.0.0-20181114050219-180f79e6909d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/highlight_go v0.0.0-20181215221002-9d8641ddf2e1/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/home v0.0.0-20190127175526-fdd6870b8ab8/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/home v0.0.0-20190204141146-5c8ae21d4240/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/home v0.0.0-20190324224244-aec053160f35/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/home v0.0.0-20190518200933-17791dca3219/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/htmlg v0.0.0-20190120222857-1e8a37b806f3/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/htmlg v0.0.0-20190503024804-b6326af49ef6/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpfs v0.0.0-20181222201310-74dc9339e414/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issues v0.0.0-20190120000219-08d8dadf8acb/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/issuesapp v0.0.0-20181229001453-b8198a402c58/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/notifications v0.0.0-20181111060504-bcc2b3082a7a/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/octicon v0.0.0-20181222203144-9ff1a4cf27f4/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/reactions v0.0.0-20181222204718-145cd5e7f3d1/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/shurcooL/webdavfs v0.0.0-20181215192745-5988b2d638f6/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/shurcooL/webdavfs v0.0.0-20190527155401-0680c3c63e3c/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= +github.com/softlayer/softlayer-go v0.0.0-20190107163317-a40f6fdd659f/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= +github.com/softlayer/softlayer-go v0.0.0-20190508182157-7c592eb2559c/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.0.0-20181109014844-d928aaea92e1/go.mod h1:XvpJiTD8NibaH7z0NzyfhR1+NQDtR9F/x92xheTwC9k= +github.com/sony/gobreaker v0.0.0-20190329013020-a9b2a3fc7395/go.mod h1:XvpJiTD8NibaH7z0NzyfhR1+NQDtR9F/x92xheTwC9k= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= +github.com/streadway/amqp v0.0.0-20181107104731-27835f1a64e9/go.mod h1:1WNBiOZtZQLpVAyu0iTduoJL9hEsMloAK5XWrtW0xdY= +github.com/streadway/amqp v0.0.0-20181205114330-a314942b2fd9/go.mod h1:1WNBiOZtZQLpVAyu0iTduoJL9hEsMloAK5XWrtW0xdY= +github.com/streadway/amqp v0.0.0-20190312223743-14f78b41ce6d/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8/go.mod h1:IlWNj9v/13q7xFbaK4mbyzMNwrZLaWSHx/aibKIZuIg= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= +github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= +github.com/testcontainers/testcontainers-go v0.0.0-20181115231424-8e868ca12c0f/go.mod h1:VoByjGpygGC4VLx9jX5V5kZ2V5qHkSEYd67SqVzGZTQ= +github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tylerb/graceful v1.2.15/go.mod h1:LPYTbOYmUTdabwRt0TGhLllQ0MUNbs0Y5q1WXJOI9II= +github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-client-go v2.16.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= +github.com/ugorji/go/codec v0.0.0-20181012064053-8333dd449516/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +github.com/ugorji/go/codec v0.0.0-20190309163734-c4a1c341dc93/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= +github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/vektah/dataloaden v0.2.0/go.mod h1:vxM6NuRlgiR0M6wbVTJeKp9vQIs81ZMfCYO+4yq/jbE= +github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= +github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= +github.com/vektah/gqlparser v1.1.0/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/vmware/govmomi v0.20.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/vmware/vic v1.4.1/go.mod h1:AiTDrZuV13NkqRzseA5ZmF2QqLpTydaaGN75xgV6Ork= +github.com/vmware/vic v1.5.2/go.mod h1:AiTDrZuV13NkqRzseA5ZmF2QqLpTydaaGN75xgV6Ork= +github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20190130112157-46e23b233c18/go.mod h1:RutfZdQAP913VY0GI8/Mjwf50+IZ7Mpg2zt3SDs17/g= +go.etcd.io/etcd v3.3.11+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= +go.etcd.io/etcd v3.3.12+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.etcd.io/etcd v3.3.13+incompatible h1:jCejD5EMnlGxFvcGRyEV4VGlENZc7oPQX6o0t7n3xbw= go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= +go.mongodb.org/mongo-driver v1.0.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.0.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= +go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.18.1-0.20181204023538-aab39bd6a98b/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.19.0/go.mod h1:AYeH0+ZxYyghG8diqaaIq/9P3VgCCt5GF2ldCY4dkFg= +go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= +go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= +go.opencensus.io v0.20.0/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +go4.org v0.0.0-20181109185143-00e24f1b2599/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +go4.org v0.0.0-20190313082347-94abd6928b1d/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +gocloud.dev v0.9.0/go.mod h1:L6ze5BTgwlPiq/4v0A/y7tVqLE/Uu31g8IL3fB09e7I= +gocloud.dev v0.10.0/go.mod h1:r5F6zlO+/+UrAYKfeMIAe8nTrjEhFPRjDm1A8wsAXUM= +gocloud.dev v0.12.0/go.mod h1:p3NTQMeV0351m3/XxEq30Fp/t7MyaUlShpKLnLz4/C0= +gocloud.dev v0.15.0/go.mod h1:ShXCyJaGrJu9y/7a6+DSCyBb9MFGZ1P5wwPa0Wu6w34= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/build v0.0.0-20190201181641-63986c177d1f/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/build v0.0.0-20190205044948-0fbf6af331f6/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= +golang.org/x/build v0.0.0-20190314215453-3ce8d48fad73/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= +golang.org/x/build v0.0.0-20190405200837-00823ba386de/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= +golang.org/x/build v0.0.0-20190530221331-2759dfe1c117/go.mod h1:fYw7AShPAhGMdXqA9gRadk/CcMsvLlClpE5oBwnS3dM= +golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190122013713-64072686203f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/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-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20181112044915-a3060d491354/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190405150703-e0c80128a2f8/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp/errors v0.0.0-20190123073158-f1c91bc264ca/go.mod h1:YgqsNsAu4fTvlab/7uiYK9LJrCIzKg/NiZUIH1/ayqo= +golang.org/x/exp/errors v0.0.0-20190510132918-efd6b22b2522/go.mod h1:YgqsNsAu4fTvlab/7uiYK9LJrCIzKg/NiZUIH1/ayqo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190314164439-f2ef6f42b753/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20190327163128-167ebed0ec6d/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190509164839-32b2708ab171/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180404174746-b3c676e531a6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327214358-63eda1eb0650/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/oauth2 v0.0.0-20170207211851-4464e7848382/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20170807180024-9a379c6b3e95/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20180603041954-1e0a3fa8ba9a/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190115181402-5dab4167f31c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190212230446-3e8b2be13635/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190523182746-aaccbc9213b0/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/perf v0.0.0-20190124201629-844a5f5b46f4/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/perf v0.0.0-20190312170614-0655857e383f/go.mod h1:FrqOtQDO3iMDVUtw5nNTDFpR1HUCGh00M3kj2wiSzLQ= +golang.org/x/perf v0.0.0-20190501051839-6835260b7148/go.mod h1:FrqOtQDO3iMDVUtw5nNTDFpR1HUCGh00M3kj2wiSzLQ= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181030150119-7e31e0c00fa0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190203050204-7ae0202eb74c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190213121743-983097b1a8a3/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222171317-cd391775e71e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190310054646-10058d7d4faa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531132440-69e3a3a65b5b h1:cuzzKXORsbIjh3IeOgOj0x2QA08ntIxmwi1mkRC3qoc= golang.org/x/sys v0.0.0-20190531132440-69e3a3a65b5b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed h1:uPxWBzB3+mlnjy9W58qY1j/cjyFjutgw/Vhan2zLy/A= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181017214349-06f26fdaaa28/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221154417-3ad2d988d5e2/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190202235157-7414d4c1f71c/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190205181801-90c8b4f75bb8/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190315043314-8781451fe3aa/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190405180640-052fc3cfdbc2/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190530171427-2b03ca6e44eb/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190530215528-75312fb06703/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190603152906-08e0b306e832/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20190129162528-20feca13ea86/go.mod h1:/lyp46tcDBI65C0XC8F4d0/XVb7MT7RScVRech7dX/4= +golang.org/x/xerrors v0.0.0-20190212162355-a5947ffaace3/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190315151331-d61658bd2e18/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20190312223110-e28f136e01a8/go.mod h1:jevfED4GnIEnJrWW55YmY9DMhajHcnkqVnEXmEtMyNI= +gonum.org/v1/gonum v0.0.0-20190314102406-9182d211c6c2/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/gonum v0.0.0-20190403090810-169ee079a3fc/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/gonum v0.0.0-20190520094443-a5f8f3a4840b/go.mod h1:zXcK6UmEkbNk22MqyPrZPx3T6fsE/O56XzkDfeYUF+Y= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190314102120-fc220b4194ca/go.mod h1:HtmWMIgmX0AAq9G6qwzC9jvu1967JZcewWVFn32Ojy4= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.0.0-20170206182103-3d017632ea10/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20180603000442-8e296ef26005/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20180829000535-087779f1d2c9/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181017004218-3f6e8463aa1d/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181021000519-a2651947f503/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= +google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180601223552-81158efcc9f2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180608181217-32ee49c4dd80/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190110221437-6909d8a4a91b/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190122154452-ba6ebe99b011/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190122204518-eef12c790cc0/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= +google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= +google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 h1:wuGevabY6r+ivPNagjUXGGxF+GqgMd+dBhjsxW4q9u4= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/grpc v0.0.0-20170208002647-2a6bf6142e96/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v0.0.0-20180920234847-8997b5fa0873/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/DataDog/dd-trace-go.v0 v0.6.1/go.mod h1:uxRvUTC61u1w+PTfyHNOzLPTCHYt6CJyGZvZSAzGvZA= +gopkg.in/DataDog/dd-trace-go.v1 v1.12.1/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= +gopkg.in/DataDog/dd-trace-go.v1 v1.14.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a h1:stTHdEoWg1pQ8riaP5ROrjS6zy6wewH/Q2iwnLCQUXY= gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= +gopkg.in/bsm/sarama-cluster.v2 v2.1.15/go.mod h1:PH+cn1N1hKueFCL+6Kz/HLj3ARW4Oop7WH3u0Ivp14w= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/go-playground/validator.v9 v9.26.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/go-playground/validator.v9 v9.27.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/go-playground/validator.v9 v9.28.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/go-playground/validator.v9 v9.29.0 h1:5ofssLNYgAA/inWn6rTZ4juWpRJUwEnXc1LG2IeXwgQ= +gopkg.in/go-playground/validator.v9 v9.29.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/gorethink/gorethink.v4 v4.1.0/go.mod h1:M7JgwrUAmshJ3iUbEK0Pt049MPyPK+CYDGGaEjdZb/c= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.39.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk= +gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= +gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/ory-am/dockertest.v2 v2.2.3/go.mod h1:kDHEsan1UcKFYH1c28sDmqnmeqIpB4Nj682gSNhYDYM= +gopkg.in/ory-am/dockertest.v3 v3.3.4/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek= +gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544/go.mod h1:UhTeH/yXCK/KY7TX24mqPkaQ7gZeqmWd/8SSS8B3aHw= gopkg.in/redis.v3 v3.6.4 h1:u7XgPH1rWwsdZnR+azldXC6x9qDU2luydOIeU/l52fE= gopkg.in/redis.v3 v3.6.4/go.mod h1:6XeGv/CrsUFDU9aVbUdNykN7k1zVmoeg83KC9RbQfiU= +gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo= gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek= gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs= gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git-fixtures.v3 v3.3.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git-fixtures.v3 v3.4.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git.v4 v4.8.1/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= +gopkg.in/src-d/go-git.v4 v4.10.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= gopkg.in/src-d/go-git.v4 v4.11.0 h1:cJwWgJ0DXifrNrXM6RGN1Y2yR60Rr1zQ9Q5DX5S9qgU= gopkg.in/src-d/go-git.v4 v4.11.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= gopkg.in/telegram-bot-api.v4 v4.6.4 h1:hpHWhzn4jTCsAJZZ2loNKfy2QWyPDRJVl3aTFXeMW8g= gopkg.in/telegram-bot-api.v4 v4.6.4/go.mod h1:5DpGO5dbumb40px+dXcwCpcjmeHNYLpk0bp3XRNvWDM= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -533,6 +1844,81 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20181108184350-ae8f1f9103cc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190215041234-466a0476246c/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190404041852-d36bf9040906/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190530170028-a1efa522b896/go.mod h1:wtc9q0E9zm8PjdRMh29DPlTlCCHVzKDwnkT4GskQVzg= +honnef.co/go/tools v0.0.0-20190531162725-42df64e2171a/go.mod h1:wtc9q0E9zm8PjdRMh29DPlTlCCHVzKDwnkT4GskQVzg= +honnef.co/go/tools v0.0.0-20190602125119-5a4a2f4a438d/go.mod h1:JlmFZigtG9vBVR3QGIQ9g/Usz4BzH+Xm6Z8iHQWRYUw= +istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= +k8s.io/api v0.0.0-20180806132203-61b11ee65332/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20181204000039-89a74a8d264d/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20181221193117-173ce66c1e39/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190126160303-ccdd560a045f/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190202010521-49be0e3344fe/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190205051315-663a691defed/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190313115550-3c12c96769cc/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190325185214-7544f9db76f6/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190327184913-92d2ee7fc726/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190405172450-8fc60343b75c/go.mod h1:3Wzsx7slWzBw6GbPMeksreuwqpC7UoolhqORRc9g5XY= +k8s.io/api v0.0.0-20190409092523-d687e77c8ae9/go.mod h1:FQEUn50aaytlU65qqBn/w+5ugllHwrBzKm7DzbnXdzE= +k8s.io/api v0.0.0-20190531161417-40a36c6fb216/go.mod h1:d5u8CK3NOXNN6UzOSp9m83FYJ1dZS6IX5KwNAEFM7Go= +k8s.io/apimachinery v0.0.0-20180821005732-488889b0007f/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190119020841-d41becfba9ee/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190126155707-0e6dcdd1b5ce/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190201131811-df262fa1a1ba/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190205091131-4b4ea28f2790/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190223001710-c182ff3b9841/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190313115320-c9defaaddf6f/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190328224500-e508a7b04a89/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190405172352-ba051b3c4d9d/go.mod h1:ELqXqns4PTTzr6t0VpyJODef88bmhBzTAkXf2Sa8Nhc= +k8s.io/apimachinery v0.0.0-20190409092423-760d1845f48b/go.mod h1:FW86P8YXVLsbuplGMZeb20J3jYHscrDqw4jELaFJvRU= +k8s.io/apimachinery v0.0.0-20190531025030-97c9a3e58b4c/go.mod h1:u/2VL7tgEMV0FFTV9q0JO+7cnTsV44LP8Pmx41R4AQ4= +k8s.io/apimachinery v0.0.0-20190531161113-d9689afd32c1/go.mod h1:u/2VL7tgEMV0FFTV9q0JO+7cnTsV44LP8Pmx41R4AQ4= +k8s.io/client-go v2.0.0-alpha.0.0.20190126161006-6134db91200e+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/client-go v8.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190327210449-e17681d19d3a/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.0.0-20181108234604-8139d8cb77af/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.0.0-20190306015804-8e90cee79f82/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.2/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190306001800-15615b16d372/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190401085232-94e1e7b7574c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190530181030-b52b5b0f5a7c/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= +k8s.io/utils v0.0.0-20190129030815-ed37f7428a91/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +k8s.io/utils v0.0.0-20190131231213-4ae6e769426e/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +k8s.io/utils v0.0.0-20190204185745-a326ccf4f02b/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +k8s.io/utils v0.0.0-20190529001817-6999998975a7/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +layeh.com/radius v0.0.0-20190118135028-0f678f039617/go.mod h1:fywZKyu//X7iRzaxLgPWsvc0L26IUpVvE/aeIL2JtIQ= +layeh.com/radius v0.0.0-20190322222518-890bc1058917/go.mod h1:fywZKyu//X7iRzaxLgPWsvc0L26IUpVvE/aeIL2JtIQ= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +pack.ag/amqp v0.8.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= +pack.ag/amqp v0.10.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= +pack.ag/amqp v0.11.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190107175209-d9ea5c54f7dc/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= +sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4= diff --git a/proxy/README.md b/proxy/README.md new file mode 100644 index 00000000..fdbeaf8c --- /dev/null +++ b/proxy/README.md @@ -0,0 +1,25 @@ +# Go Proxy [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/micro/go-proxy?status.svg)](https://godoc.org/github.com/micro/go-proxy) + +Go Proxy is a proxy library for Go Micro. + +## Overview + +Go Micro is a distributed systems framework for client/server communication. It handles the details +around discovery, fault tolerance, rpc communication, etc. We may want to leverage this in broader ecosystems +which make use of standard http or we may also want to offload a number of requirements to a single proxy. + +## Features + +- **Transparent Proxy** - Proxy requests to any micro services through a single location. Go Proxy enables +you to write Go Micro proxies which handle and forward requests. This is good for incorporating wrappers. + +- **Single Backend Router** - Enable the single backend router to proxy directly to your local app. The proxy +allows you to set a router which serves your backend service whether its http, grpc, etc. + +- **Protocol Aware Handler** - Set a request handler which speaks your app protocol to make outbound requests. +Your app may not speak the MUCP protocol so it may be easier to translate internally. + +- **Control Planes** - Additionally we support use of control planes to offload many distributed systems concerns. + * [x] [Consul](https://www.consul.io/docs/connect/native.html) - Using Connect-Native to provide secure mTLS. + * [x] [NATS](https://nats.io/) - Fully leveraging NATS as the control plane and data plane. + diff --git a/proxy/control/consul/consul.go b/proxy/control/consul/consul.go new file mode 100644 index 00000000..cefb4b23 --- /dev/null +++ b/proxy/control/consul/consul.go @@ -0,0 +1,77 @@ +// Package consul provides Consul Connect control plane +package consul + +import ( + "log" + + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/connect" + "github.com/micro/go-micro" + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/registry/consul" + "github.com/micro/go-micro/transport" +) + +type proxyService struct { + c *connect.Service + micro.Service +} + +func newService(opts ...micro.Option) micro.Service { + // we need to use the consul registry to register connect applications + r := consul.NewRegistry( + consul.Connect(), + ) + + // pass in the registry as part of our options + newOpts := append([]micro.Option{micro.Registry(r)}, opts...) + + // service := micro.NewService(newOpts...) + service := micro.NewService(newOpts...) + + // get the consul address + addrs := service.Server().Options().Registry.Options().Addrs + + // set the config + config := api.DefaultConfig() + if len(addrs) > 0 { + config.Address = addrs[0] + } + + // create consul client + client, err := api.NewClient(api.DefaultConfig()) + if err != nil { + log.Fatal(err) + } + + // create connect service using the service name + svc, err := connect.NewService(service.Server().Options().Name, client) + if err != nil { + log.Fatal(err) + } + + // setup transport tls config + service.Options().Transport.Init( + transport.TLSConfig(svc.ServerTLSConfig()), + ) + + // setup broker tls config + service.Options().Broker.Init( + broker.TLSConfig(svc.ServerTLSConfig()), + ) + + // return a new proxy enabled service + return &proxyService{ + c: svc, + Service: service, + } +} + +func (p *proxyService) String() string { + return "consul" +} + +// NewService returns a Consul Connect-Native micro.Service +func NewService(opts ...micro.Option) micro.Service { + return newService(opts...) +} diff --git a/proxy/control/nats/nats.go b/proxy/control/nats/nats.go new file mode 100644 index 00000000..c576d525 --- /dev/null +++ b/proxy/control/nats/nats.go @@ -0,0 +1,30 @@ +// Package nats provides a NATS control plane +package nats + +import ( + "github.com/micro/go-micro" + broker "github.com/micro/go-plugins/broker/nats" + registry "github.com/micro/go-plugins/registry/nats" + transport "github.com/micro/go-plugins/transport/nats" +) + +// NewService returns a NATS micro.Service +func NewService(opts ...micro.Option) micro.Service { + // initialise nats components + b := broker.NewBroker() + r := registry.NewRegistry() + t := transport.NewTransport() + + // create new options + options := []micro.Option{ + micro.Broker(b), + micro.Registry(r), + micro.Transport(t), + } + + // append user options + options = append(options, opts...) + + // return a nats service + return micro.NewService(options...) +} diff --git a/proxy/proto/.proxy.proto.swp b/proxy/proto/.proxy.proto.swp new file mode 100644 index 00000000..ba59da91 Binary files /dev/null and b/proxy/proto/.proxy.proto.swp differ diff --git a/proxy/proto/proxy.micro.go b/proxy/proto/proxy.micro.go new file mode 100644 index 00000000..ff603415 --- /dev/null +++ b/proxy/proto/proxy.micro.go @@ -0,0 +1,207 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: github.com/micro/go-proxy/proto/proxy.proto + +/* +Package proxy is a generated protocol buffer package. + +It is generated from these files: + github.com/micro/go-proxy/proto/proxy.proto + +It has these top-level messages: + Request + Response + Message + Empty +*/ +package proxy + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "context" + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/server" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ client.Option +var _ server.Option + +// Client API for Service service + +type Service interface { + Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) + Stream(ctx context.Context, opts ...client.CallOption) (Service_StreamService, error) + Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Empty, error) +} + +type service struct { + c client.Client + name string +} + +func NewService(name string, c client.Client) Service { + if c == nil { + c = client.NewClient() + } + if len(name) == 0 { + name = "proxy" + } + return &service{ + c: c, + name: name, + } +} + +func (c *service) Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) { + req := c.c.NewRequest(c.name, "Service.Call", in) + out := new(Response) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *service) Stream(ctx context.Context, opts ...client.CallOption) (Service_StreamService, error) { + req := c.c.NewRequest(c.name, "Service.Stream", &Request{}) + stream, err := c.c.Stream(ctx, req, opts...) + if err != nil { + return nil, err + } + return &serviceStream{stream}, nil +} + +type Service_StreamService interface { + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Send(*Request) error + Recv() (*Response, error) +} + +type serviceStream struct { + stream client.Stream +} + +func (x *serviceStream) Close() error { + return x.stream.Close() +} + +func (x *serviceStream) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *serviceStream) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *serviceStream) Send(m *Request) error { + return x.stream.Send(m) +} + +func (x *serviceStream) Recv() (*Response, error) { + m := new(Response) + err := x.stream.Recv(m) + if err != nil { + return nil, err + } + return m, nil +} + +func (c *service) Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Empty, error) { + req := c.c.NewRequest(c.name, "Service.Publish", in) + out := new(Empty) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Service service + +type ServiceHandler interface { + Call(context.Context, *Request, *Response) error + Stream(context.Context, Service_StreamStream) error + Publish(context.Context, *Message, *Empty) error +} + +func RegisterServiceHandler(s server.Server, hdlr ServiceHandler, opts ...server.HandlerOption) error { + type service interface { + Call(ctx context.Context, in *Request, out *Response) error + Stream(ctx context.Context, stream server.Stream) error + Publish(ctx context.Context, in *Message, out *Empty) error + } + type Service struct { + service + } + h := &serviceHandler{hdlr} + return s.Handle(s.NewHandler(&Service{h}, opts...)) +} + +type serviceHandler struct { + ServiceHandler +} + +func (h *serviceHandler) Call(ctx context.Context, in *Request, out *Response) error { + return h.ServiceHandler.Call(ctx, in, out) +} + +func (h *serviceHandler) Stream(ctx context.Context, stream server.Stream) error { + return h.ServiceHandler.Stream(ctx, &serviceStreamStream{stream}) +} + +type Service_StreamStream interface { + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Send(*Response) error + Recv() (*Request, error) +} + +type serviceStreamStream struct { + stream server.Stream +} + +func (x *serviceStreamStream) Close() error { + return x.stream.Close() +} + +func (x *serviceStreamStream) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *serviceStreamStream) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *serviceStreamStream) Send(m *Response) error { + return x.stream.Send(m) +} + +func (x *serviceStreamStream) Recv() (*Request, error) { + m := new(Request) + if err := x.stream.Recv(m); err != nil { + return nil, err + } + return m, nil +} + +func (h *serviceHandler) Publish(ctx context.Context, in *Message, out *Empty) error { + return h.ServiceHandler.Publish(ctx, in, out) +} diff --git a/proxy/proto/proxy.pb.go b/proxy/proto/proxy.pb.go new file mode 100644 index 00000000..75c9d76e --- /dev/null +++ b/proxy/proto/proxy.pb.go @@ -0,0 +1,345 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: github.com/micro/go-proxy/proto/proxy.proto + +package proxy + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type Request struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Request) Reset() { *m = Request{} } +func (m *Request) String() string { return proto.CompactTextString(m) } +func (*Request) ProtoMessage() {} +func (*Request) Descriptor() ([]byte, []int) { + return fileDescriptor_242fe7b9d67fe7d7, []int{0} +} + +func (m *Request) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Request.Unmarshal(m, b) +} +func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Request.Marshal(b, m, deterministic) +} +func (m *Request) XXX_Merge(src proto.Message) { + xxx_messageInfo_Request.Merge(m, src) +} +func (m *Request) XXX_Size() int { + return xxx_messageInfo_Request.Size(m) +} +func (m *Request) XXX_DiscardUnknown() { + xxx_messageInfo_Request.DiscardUnknown(m) +} + +var xxx_messageInfo_Request proto.InternalMessageInfo + +type Response struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} +func (*Response) Descriptor() ([]byte, []int) { + return fileDescriptor_242fe7b9d67fe7d7, []int{1} +} + +func (m *Response) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Response.Unmarshal(m, b) +} +func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Response.Marshal(b, m, deterministic) +} +func (m *Response) XXX_Merge(src proto.Message) { + xxx_messageInfo_Response.Merge(m, src) +} +func (m *Response) XXX_Size() int { + return xxx_messageInfo_Response.Size(m) +} +func (m *Response) XXX_DiscardUnknown() { + xxx_messageInfo_Response.DiscardUnknown(m) +} + +var xxx_messageInfo_Response proto.InternalMessageInfo + +type Message struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} +func (*Message) Descriptor() ([]byte, []int) { + return fileDescriptor_242fe7b9d67fe7d7, []int{2} +} + +func (m *Message) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Message.Unmarshal(m, b) +} +func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Message.Marshal(b, m, deterministic) +} +func (m *Message) XXX_Merge(src proto.Message) { + xxx_messageInfo_Message.Merge(m, src) +} +func (m *Message) XXX_Size() int { + return xxx_messageInfo_Message.Size(m) +} +func (m *Message) XXX_DiscardUnknown() { + xxx_messageInfo_Message.DiscardUnknown(m) +} + +var xxx_messageInfo_Message proto.InternalMessageInfo + +type Empty struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Empty) Reset() { *m = Empty{} } +func (m *Empty) String() string { return proto.CompactTextString(m) } +func (*Empty) ProtoMessage() {} +func (*Empty) Descriptor() ([]byte, []int) { + return fileDescriptor_242fe7b9d67fe7d7, []int{3} +} + +func (m *Empty) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Empty.Unmarshal(m, b) +} +func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Empty.Marshal(b, m, deterministic) +} +func (m *Empty) XXX_Merge(src proto.Message) { + xxx_messageInfo_Empty.Merge(m, src) +} +func (m *Empty) XXX_Size() int { + return xxx_messageInfo_Empty.Size(m) +} +func (m *Empty) XXX_DiscardUnknown() { + xxx_messageInfo_Empty.DiscardUnknown(m) +} + +var xxx_messageInfo_Empty proto.InternalMessageInfo + +func init() { + proto.RegisterType((*Request)(nil), "proxy.Request") + proto.RegisterType((*Response)(nil), "proxy.Response") + proto.RegisterType((*Message)(nil), "proxy.Message") + proto.RegisterType((*Empty)(nil), "proxy.Empty") +} + +func init() { + proto.RegisterFile("github.com/micro/go-proxy/proto/proxy.proto", fileDescriptor_242fe7b9d67fe7d7) +} + +var fileDescriptor_242fe7b9d67fe7d7 = []byte{ + // 186 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4e, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0xcf, 0xcd, 0x4c, 0x2e, 0xca, 0xd7, 0x4f, 0xcf, 0xd7, + 0x2d, 0x28, 0xca, 0xaf, 0xa8, 0xd4, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x07, 0xb3, 0xf5, 0xc0, + 0x6c, 0x21, 0x56, 0x30, 0x47, 0x89, 0x93, 0x8b, 0x3d, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44, + 0x89, 0x8b, 0x8b, 0x23, 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x15, 0x24, 0xec, 0x9b, 0x5a, + 0x5c, 0x9c, 0x98, 0x9e, 0xaa, 0xc4, 0xce, 0xc5, 0xea, 0x9a, 0x5b, 0x50, 0x52, 0x69, 0x34, 0x81, + 0x91, 0x8b, 0x3d, 0x38, 0xb5, 0xa8, 0x2c, 0x33, 0x39, 0x55, 0x48, 0x93, 0x8b, 0xc5, 0x39, 0x31, + 0x27, 0x47, 0x88, 0x4f, 0x0f, 0x62, 0x26, 0xd4, 0x0c, 0x29, 0x7e, 0x38, 0x1f, 0x6a, 0x10, 0x83, + 0x90, 0x3e, 0x17, 0x5b, 0x70, 0x49, 0x51, 0x6a, 0x62, 0x2e, 0x11, 0x8a, 0x35, 0x18, 0x0d, 0x18, + 0x85, 0x34, 0xb9, 0xd8, 0x03, 0x4a, 0x93, 0x72, 0x32, 0x8b, 0x33, 0xe0, 0x3a, 0xa0, 0x6e, 0x91, + 0xe2, 0x81, 0xf2, 0xc1, 0x0e, 0x52, 0x62, 0x48, 0x62, 0x03, 0xfb, 0xc5, 0x18, 0x10, 0x00, 0x00, + 0xff, 0xff, 0x51, 0x0d, 0x40, 0x94, 0xfa, 0x00, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// ServiceClient is the client API for Service service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ServiceClient interface { + Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + Stream(ctx context.Context, opts ...grpc.CallOption) (Service_StreamClient, error) + Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Empty, error) +} + +type serviceClient struct { + cc *grpc.ClientConn +} + +func NewServiceClient(cc *grpc.ClientConn) ServiceClient { + return &serviceClient{cc} +} + +func (c *serviceClient) Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/proxy.Service/Call", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) Stream(ctx context.Context, opts ...grpc.CallOption) (Service_StreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_Service_serviceDesc.Streams[0], "/proxy.Service/Stream", opts...) + if err != nil { + return nil, err + } + x := &serviceStreamClient{stream} + return x, nil +} + +type Service_StreamClient interface { + Send(*Request) error + Recv() (*Response, error) + grpc.ClientStream +} + +type serviceStreamClient struct { + grpc.ClientStream +} + +func (x *serviceStreamClient) Send(m *Request) error { + return x.ClientStream.SendMsg(m) +} + +func (x *serviceStreamClient) Recv() (*Response, error) { + m := new(Response) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *serviceClient) Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/proxy.Service/Publish", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ServiceServer is the server API for Service service. +type ServiceServer interface { + Call(context.Context, *Request) (*Response, error) + Stream(Service_StreamServer) error + Publish(context.Context, *Message) (*Empty, error) +} + +func RegisterServiceServer(s *grpc.Server, srv ServiceServer) { + s.RegisterService(&_Service_serviceDesc, srv) +} + +func _Service_Call_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ServiceServer).Call(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proxy.Service/Call", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ServiceServer).Call(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _Service_Stream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(ServiceServer).Stream(&serviceStreamServer{stream}) +} + +type Service_StreamServer interface { + Send(*Response) error + Recv() (*Request, error) + grpc.ServerStream +} + +type serviceStreamServer struct { + grpc.ServerStream +} + +func (x *serviceStreamServer) Send(m *Response) error { + return x.ServerStream.SendMsg(m) +} + +func (x *serviceStreamServer) Recv() (*Request, error) { + m := new(Request) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Service_Publish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Message) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ServiceServer).Publish(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proxy.Service/Publish", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ServiceServer).Publish(ctx, req.(*Message)) + } + return interceptor(ctx, in, info, handler) +} + +var _Service_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proxy.Service", + HandlerType: (*ServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Call", + Handler: _Service_Call_Handler, + }, + { + MethodName: "Publish", + Handler: _Service_Publish_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Stream", + Handler: _Service_Stream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "github.com/micro/go-proxy/proto/proxy.proto", +} diff --git a/proxy/proto/proxy.proto b/proxy/proto/proxy.proto new file mode 100644 index 00000000..0c9e12ff --- /dev/null +++ b/proxy/proto/proxy.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package proxy; + +service Service { + rpc Call(Request) returns (Response) {}; + rpc Stream(stream Request) returns (stream Response) {}; + rpc Publish(Message) returns (Empty) {}; + rpc Subscribe(Message) returns (stream Message) {}; +} + +message Request {} + +message Response {} + +message Message {} + +message Empty {} diff --git a/proxy/router/grpc/grpc.go b/proxy/router/grpc/grpc.go new file mode 100644 index 00000000..e209f3c3 --- /dev/null +++ b/proxy/router/grpc/grpc.go @@ -0,0 +1,235 @@ +// Package grpc transparently forwards the grpc protocol using a go-micro client. +package grpc + +import ( + "context" + "io" + "strings" + + "github.com/micro/go-micro" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/codec/bytes" + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/service/grpc" +) + +// Router will transparently proxy requests to the backend. +// If no backend is specified it will call a service using the client. +// If the service matches the Name it will use the server.DefaultRouter. +type Router struct { + // Name of the local service. In the event it's to be left alone + Name string + + // Backend is a single backend to route to + // If backend is of the form address:port it will call the address. + // Otherwise it will use it as the service name to call. + Backend string + + // Endpoint specified the fixed endpoint to call. + // In the event you proxy to a fixed backend this lets you + // call a single endpoint + Endpoint string + + // The client to use for outbound requests + Client client.Client +} + +var ( + // The default name of this local service + DefaultName = "go.micro.proxy" + // The default router + DefaultRouter = &Router{} +) + +// read client request and write to server +func readLoop(r server.Request, s client.Stream) error { + // request to backend server + req := s.Request() + + for { + // get data from client + // no need to decode it + body, err := r.Read() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + + // get the header from client + hdr := r.Header() + msg := &codec.Message{ + Type: codec.Request, + Header: hdr, + Body: body, + } + // write the raw request + err = req.Codec().Write(msg, nil) + if err == io.EOF { + return nil + } else if err != nil { + return err + } + } +} + +// ServeRequest honours the server.Router interface +func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error { + // set the default name e.g local proxy + if p.Name == "" { + p.Name = DefaultName + } + + // set default client + if p.Client == nil { + p.Client = client.DefaultClient + } + + // check service route + if req.Service() == p.Name { + // use the default router + return server.DefaultRouter.ServeRequest(ctx, req, rsp) + } + + opts := []client.CallOption{} + + // service name + service := req.Service() + endpoint := req.Endpoint() + + // call a specific backend + if len(p.Backend) > 0 { + // address:port + if parts := strings.Split(p.Backend, ":"); len(parts) > 0 { + opts = append(opts, client.WithAddress(p.Backend)) + // use as service name + } else { + service = p.Backend + } + } + + // call a specific endpoint + if len(p.Endpoint) > 0 { + endpoint = p.Endpoint + } + + // read initial request + body, err := req.Read() + if err != nil { + return err + } + + // create new request with raw bytes body + creq := p.Client.NewRequest(service, endpoint, &bytes.Frame{body}, client.WithContentType(req.ContentType())) + + // create new stream + stream, err := p.Client.Stream(ctx, creq, opts...) + if err != nil { + return err + } + defer stream.Close() + + // create client request read loop + go readLoop(req, stream) + + // get raw response + resp := stream.Response() + + // create server response write loop + for { + // read backend response body + body, err := resp.Read() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + + // read backend response header + hdr := resp.Header() + + // write raw response header to client + rsp.WriteHeader(hdr) + + // write raw response body to client + err = rsp.Write(body) + if err == io.EOF { + return nil + } else if err != nil { + return err + } + } + + return nil +} + +// NewSingleHostRouter returns a router which sends requests to a single backend +// +// It is used by setting it in a new micro service to act as a proxy for a backend. +// +// Usage: +// +// Create a new router to the http backend +// +// r := NewSingleHostRouter("localhost:10001") +// +// // Create your new service +// service := micro.NewService( +// micro.Name("greeter"), +// // Set the router +// http.WithRouter(r), +// ) +// +// // Run the service +// service.Run() +func NewSingleHostRouter(url string) *Router { + return &Router{ + Backend: url, + } +} + +// NewService returns a new proxy. It acts as a micro service proxy. +// Any request on the transport is routed to via the client to a service. +// In the event a backend is specified then it routes to that backend. +// The name of the backend can be a local address:port or a service name. +// +// Usage: +// +// New micro proxy routes via micro client to any service +// +// proxy := NewService() +// +// OR with address:port routes to local service +// +// service := NewService( +// // Sets the default http endpoint +// proxy.WithBackend("localhost:10001"), +// ) +// +// OR with service name routes to a fixed backend service +// +// service := NewService( +// // Sets the backend service +// proxy.WithBackend("greeter"), +// ) +// +func NewService(opts ...micro.Option) micro.Service { + router := DefaultRouter + name := DefaultName + + // prepend router to opts + opts = append([]micro.Option{ + micro.Name(name), + WithRouter(router), + }, opts...) + + // create the new service + service := grpc.NewService(opts...) + + // set router name + router.Name = service.Server().Options().Name + + return service +} diff --git a/proxy/router/grpc/options.go b/proxy/router/grpc/options.go new file mode 100644 index 00000000..7ead4364 --- /dev/null +++ b/proxy/router/grpc/options.go @@ -0,0 +1,32 @@ +package grpc + +import ( + "github.com/micro/go-micro" + "github.com/micro/go-micro/server" +) + +// WithBackend provides an option to set the proxy backend url +func WithBackend(url string) micro.Option { + return func(o *micro.Options) { + // get the router + r := o.Server.Options().Router + + // not set + if r == nil { + r = DefaultRouter + o.Server.Init(server.WithRouter(r)) + } + + // check its a proxy router + if proxyRouter, ok := r.(*Router); ok { + proxyRouter.Backend = url + } + } +} + +// WithRouter provides an option to set the proxy router +func WithRouter(r server.Router) micro.Option { + return func(o *micro.Options) { + o.Server.Init(server.WithRouter(r)) + } +} diff --git a/proxy/router/http/http.go b/proxy/router/http/http.go new file mode 100644 index 00000000..2a079191 --- /dev/null +++ b/proxy/router/http/http.go @@ -0,0 +1,177 @@ +// Package http provides a micro rpc to http proxy +package http + +import ( + "bytes" + "context" + "io" + "io/ioutil" + "net/http" + "net/url" + "path" + + "github.com/micro/go-micro" + "github.com/micro/go-micro/errors" + "github.com/micro/go-micro/server" +) + +// Router will proxy rpc requests as http POST requests. It is a server.Router +type Router struct { + // The http backend to call + Backend string + + // first request + first bool +} + +var ( + // The default backend + DefaultBackend = "http://localhost:9090" + // The default router + DefaultRouter = &Router{} +) + +func getMethod(hdr map[string]string) string { + switch hdr["Micro-Method"] { + case "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH": + return hdr["Micro-Method"] + default: + return "POST" + } +} + +func getEndpoint(hdr map[string]string) string { + ep := hdr["Micro-Endpoint"] + if len(ep) > 0 && ep[0] == '/' { + return ep + } + return "" +} + +// ServeRequest honours the server.Router interface +func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error { + if p.Backend == "" { + p.Backend = DefaultBackend + } + + for { + // get data + body, err := req.Read() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + + // get the header + hdr := req.Header() + + // get method + method := getMethod(hdr) + + // get endpoint + endpoint := getEndpoint(hdr) + + // set the endpoint + if len(endpoint) == 0 { + endpoint = p.Backend + } else { + // add endpoint to backend + u, err := url.Parse(p.Backend) + if err != nil { + return errors.InternalServerError(req.Service(), err.Error()) + } + u.Path = path.Join(u.Path, endpoint) + endpoint = u.String() + } + + // send to backend + hreq, err := http.NewRequest(method, endpoint, bytes.NewReader(body)) + if err != nil { + return errors.InternalServerError(req.Service(), err.Error()) + } + + // set the headers + for k, v := range hdr { + hreq.Header.Set(k, v) + } + + // make the call + hrsp, err := http.DefaultClient.Do(hreq) + if err != nil { + return errors.InternalServerError(req.Service(), err.Error()) + } + + // read body + b, err := ioutil.ReadAll(hrsp.Body) + hrsp.Body.Close() + if err != nil { + return errors.InternalServerError(req.Service(), err.Error()) + } + + // set response headers + hdr = map[string]string{} + for k, _ := range hrsp.Header { + hdr[k] = hrsp.Header.Get(k) + } + // write the header + rsp.WriteHeader(hdr) + // write the body + err = rsp.Write(b) + if err == io.EOF { + return nil + } + if err != nil { + return errors.InternalServerError(req.Service(), err.Error()) + } + } + + return nil +} + +// NewSingleHostRouter returns a router which sends requests to a single http backend +// +// It is used by setting it in a new micro service to act as a proxy for a http backend. +// +// Usage: +// +// Create a new router to the http backend +// +// r := NewSingleHostRouter("http://localhost:10001") +// +// // Create your new service +// service := micro.NewService( +// micro.Name("greeter"), +// // Set the router +// http.WithRouter(r), +// ) +// +// // Run the service +// service.Run() +func NewSingleHostRouter(url string) *Router { + return &Router{ + Backend: url, + } +} + +// NewService returns a new http proxy. It acts as a micro service proxy. +// Any request on the transport is routed to a fixed http backend. +// +// Usage: +// +// service := NewService( +// micro.Name("greeter"), +// // Sets the default http endpoint +// http.WithBackend("http://localhost:10001"), +// ) +// +func NewService(opts ...micro.Option) micro.Service { + // prepend router to opts + opts = append([]micro.Option{ + WithRouter(DefaultRouter), + }, opts...) + + // create the new service + return micro.NewService(opts...) +} diff --git a/proxy/router/http/http_test.go b/proxy/router/http/http_test.go new file mode 100644 index 00000000..f06b6b8d --- /dev/null +++ b/proxy/router/http/http_test.go @@ -0,0 +1,122 @@ +package http + +import ( + "context" + "fmt" + "net" + "net/http" + "sync" + "testing" + + "github.com/micro/go-micro" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/registry/memory" + "github.com/micro/go-micro/server" +) + +type testHandler struct{} + +func (t *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"hello": "world"}`)) +} + +func TestHTTPRouter(t *testing.T) { + c, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatal(err) + } + defer c.Close() + addr := c.Addr().String() + + url := fmt.Sprintf("http://%s", addr) + + testCases := []struct { + // http endpoint to call e.g /foo/bar + httpEp string + // rpc endpoint called e.g Foo.Bar + rpcEp string + // should be an error + err bool + }{ + {"/", "Foo.Bar", false}, + {"/", "Foo.Baz", false}, + {"/helloworld", "Hello.World", true}, + } + + // handler + http.Handle("/", new(testHandler)) + + // new proxy + p := NewSingleHostRouter(url) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var wg sync.WaitGroup + wg.Add(1) + + // new micro service + service := micro.NewService( + micro.Context(ctx), + micro.Name("foobar"), + micro.Registry(memory.NewRegistry()), + micro.AfterStart(func() error { + wg.Done() + return nil + }), + ) + + // set router + service.Server().Init( + server.WithRouter(p), + ) + + // run service + // server + go http.Serve(c, nil) + go service.Run() + + // wait till service is started + wg.Wait() + + for _, test := range testCases { + req := service.Client().NewRequest("foobar", test.rpcEp, map[string]string{"foo": "bar"}, client.WithContentType("application/json")) + var rsp map[string]string + err := service.Client().Call(ctx, req, &rsp) + if err != nil && test.err == false { + t.Fatal(err) + } + if v := rsp["hello"]; v != "world" { + t.Fatalf("Expected hello world got %s from %s", v, test.rpcEp) + } + } +} + +func TestHTTPRouterOptions(t *testing.T) { + // test endpoint + service := NewService( + WithBackend("http://foo.bar"), + ) + + r := service.Server().Options().Router + httpRouter, ok := r.(*Router) + if !ok { + t.Fatal("Expected http router to be installed") + } + if httpRouter.Backend != "http://foo.bar" { + t.Fatalf("Expected endpoint http://foo.bar got %v", httpRouter.Backend) + } + + // test router + service = NewService( + WithRouter(&Router{Backend: "http://foo2.bar"}), + ) + r = service.Server().Options().Router + httpRouter, ok = r.(*Router) + if !ok { + t.Fatal("Expected http router to be installed") + } + if httpRouter.Backend != "http://foo2.bar" { + t.Fatalf("Expected endpoint http://foo2.bar got %v", httpRouter.Backend) + } +} diff --git a/proxy/router/http/options.go b/proxy/router/http/options.go new file mode 100644 index 00000000..06b8af2d --- /dev/null +++ b/proxy/router/http/options.go @@ -0,0 +1,32 @@ +package http + +import ( + "github.com/micro/go-micro" + "github.com/micro/go-micro/server" +) + +// WithBackend provides an option to set the http backend url +func WithBackend(url string) micro.Option { + return func(o *micro.Options) { + // get the router + r := o.Server.Options().Router + + // not set + if r == nil { + r = DefaultRouter + o.Server.Init(server.WithRouter(r)) + } + + // check its a http router + if httpRouter, ok := r.(*Router); ok { + httpRouter.Backend = url + } + } +} + +// WithRouter provides an option to set the http router +func WithRouter(r server.Router) micro.Option { + return func(o *micro.Options) { + o.Server.Init(server.WithRouter(r)) + } +} diff --git a/proxy/router/mucp/mucp.go b/proxy/router/mucp/mucp.go new file mode 100644 index 00000000..569a3bb4 --- /dev/null +++ b/proxy/router/mucp/mucp.go @@ -0,0 +1,234 @@ +// Package mucp transparently forwards the incoming request using a go-micro client. +package mucp + +import ( + "context" + "io" + "strings" + + "github.com/micro/go-micro" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/codec/bytes" + "github.com/micro/go-micro/server" +) + +// Router will transparently proxy requests to the backend. +// If no backend is specified it will call a service using the client. +// If the service matches the Name it will use the server.DefaultRouter. +type Router struct { + // Name of the local service. In the event it's to be left alone + Name string + + // Backend is a single backend to route to + // If backend is of the form address:port it will call the address. + // Otherwise it will use it as the service name to call. + Backend string + + // Endpoint specified the fixed endpoint to call. + // In the event you proxy to a fixed backend this lets you + // call a single endpoint + Endpoint string + + // The client to use for outbound requests + Client client.Client +} + +var ( + // The default name of this local service + DefaultName = "go.micro.proxy" + // The default router + DefaultRouter = &Router{} +) + +// read client request and write to server +func readLoop(r server.Request, s client.Stream) error { + // request to backend server + req := s.Request() + + for { + // get data from client + // no need to decode it + body, err := r.Read() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + + // get the header from client + hdr := r.Header() + msg := &codec.Message{ + Type: codec.Request, + Header: hdr, + Body: body, + } + // write the raw request + err = req.Codec().Write(msg, nil) + if err == io.EOF { + return nil + } else if err != nil { + return err + } + } +} + +// ServeRequest honours the server.Router interface +func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error { + // set the default name e.g local proxy + if p.Name == "" { + p.Name = DefaultName + } + + // set default client + if p.Client == nil { + p.Client = client.DefaultClient + } + + // check service route + if req.Service() == p.Name { + // use the default router + return server.DefaultRouter.ServeRequest(ctx, req, rsp) + } + + opts := []client.CallOption{} + + // service name + service := req.Service() + endpoint := req.Endpoint() + + // call a specific backend + if len(p.Backend) > 0 { + // address:port + if parts := strings.Split(p.Backend, ":"); len(parts) > 0 { + opts = append(opts, client.WithAddress(p.Backend)) + // use as service name + } else { + service = p.Backend + } + } + + // call a specific endpoint + if len(p.Endpoint) > 0 { + endpoint = p.Endpoint + } + + // read initial request + body, err := req.Read() + if err != nil { + return err + } + + // create new request with raw bytes body + creq := p.Client.NewRequest(service, endpoint, &bytes.Frame{body}, client.WithContentType(req.ContentType())) + + // create new stream + stream, err := p.Client.Stream(ctx, creq, opts...) + if err != nil { + return err + } + defer stream.Close() + + // create client request read loop + go readLoop(req, stream) + + // get raw response + resp := stream.Response() + + // create server response write loop + for { + // read backend response body + body, err := resp.Read() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + + // read backend response header + hdr := resp.Header() + + // write raw response header to client + rsp.WriteHeader(hdr) + + // write raw response body to client + err = rsp.Write(body) + if err == io.EOF { + return nil + } else if err != nil { + return err + } + } + + return nil +} + +// NewSingleHostRouter returns a router which sends requests to a single backend +// +// It is used by setting it in a new micro service to act as a proxy for a backend. +// +// Usage: +// +// Create a new router to the http backend +// +// r := NewSingleHostRouter("localhost:10001") +// +// // Create your new service +// service := micro.NewService( +// micro.Name("greeter"), +// // Set the router +// http.WithRouter(r), +// ) +// +// // Run the service +// service.Run() +func NewSingleHostRouter(url string) *Router { + return &Router{ + Backend: url, + } +} + +// NewService returns a new proxy. It acts as a micro service proxy. +// Any request on the transport is routed to via the client to a service. +// In the event a backend is specified then it routes to that backend. +// The name of the backend can be a local address:port or a service name. +// +// Usage: +// +// New micro proxy routes via micro client to any service +// +// proxy := NewService() +// +// OR with address:port routes to local service +// +// service := NewService( +// // Sets the default http endpoint +// proxy.WithBackend("localhost:10001"), +// ) +// +// OR with service name routes to a fixed backend service +// +// service := NewService( +// // Sets the backend service +// proxy.WithBackend("greeter"), +// ) +// +func NewService(opts ...micro.Option) micro.Service { + router := DefaultRouter + name := DefaultName + + // prepend router to opts + opts = append([]micro.Option{ + micro.Name(name), + WithRouter(router), + }, opts...) + + // create the new service + service := micro.NewService(opts...) + + // set router name + router.Name = service.Server().Options().Name + + return service +} diff --git a/proxy/router/mucp/options.go b/proxy/router/mucp/options.go new file mode 100644 index 00000000..42bad23b --- /dev/null +++ b/proxy/router/mucp/options.go @@ -0,0 +1,32 @@ +package mucp + +import ( + "github.com/micro/go-micro" + "github.com/micro/go-micro/server" +) + +// WithBackend provides an option to set the proxy backend url +func WithBackend(url string) micro.Option { + return func(o *micro.Options) { + // get the router + r := o.Server.Options().Router + + // not set + if r == nil { + r = DefaultRouter + o.Server.Init(server.WithRouter(r)) + } + + // check its a proxy router + if proxyRouter, ok := r.(*Router); ok { + proxyRouter.Backend = url + } + } +} + +// WithRouter provides an option to set the proxy router +func WithRouter(r server.Router) micro.Option { + return func(o *micro.Options) { + o.Server.Init(server.WithRouter(r)) + } +} diff --git a/server/grpc/README.md b/server/grpc/README.md new file mode 100644 index 00000000..d53fc58d --- /dev/null +++ b/server/grpc/README.md @@ -0,0 +1,30 @@ +# GRPC Server + +The grpc server is a [micro.Server](https://godoc.org/github.com/micro/go-micro/server#Server) compatible server. + +## Overview + +The server makes use of the [google.golang.org/grpc](google.golang.org/grpc) framework for the underlying server +but continues to use micro handler signatures and protoc-gen-micro generated code. + +## Usage + +Specify the server to your micro service + +```go +import ( + "github.com/micro/go-micro" + "github.com/micro/go-plugins/server/grpc" +) + +func main() { + service := micro.NewService( + // This needs to be first as it replaces the underlying server + // which causes any configuration set before it + // to be discarded + micro.Server(grpc.NewServer()), + micro.Name("greeter"), + ) +} +``` +**NOTE**: Setting the gRPC server and/or client causes the underlying the server/client to be replaced which causes any previous configuration set on that server/client to be discarded. It is therefore recommended to set gRPC server/client before any other configuration \ No newline at end of file diff --git a/server/grpc/buffer.go b/server/grpc/buffer.go new file mode 100644 index 00000000..c43bb231 --- /dev/null +++ b/server/grpc/buffer.go @@ -0,0 +1,14 @@ +package grpc + +import ( + "bytes" +) + +type buffer struct { + *bytes.Buffer +} + +func (b *buffer) Close() error { + b.Buffer.Reset() + return nil +} diff --git a/server/grpc/codec.go b/server/grpc/codec.go new file mode 100644 index 00000000..50a96ef4 --- /dev/null +++ b/server/grpc/codec.go @@ -0,0 +1,82 @@ +package grpc + +import ( + "encoding/json" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/codec/jsonrpc" + "github.com/micro/go-micro/codec/protorpc" + "google.golang.org/grpc/encoding" +) + +type jsonCodec struct{} +type bytesCodec struct{} +type protoCodec struct{} + +var ( + defaultGRPCCodecs = map[string]encoding.Codec{ + "application/json": jsonCodec{}, + "application/proto": protoCodec{}, + "application/protobuf": protoCodec{}, + "application/octet-stream": protoCodec{}, + "application/grpc": protoCodec{}, + "application/grpc+json": jsonCodec{}, + "application/grpc+proto": protoCodec{}, + "application/grpc+bytes": bytesCodec{}, + } + + defaultRPCCodecs = map[string]codec.NewCodec{ + "application/json": jsonrpc.NewCodec, + "application/json-rpc": jsonrpc.NewCodec, + "application/protobuf": protorpc.NewCodec, + "application/proto-rpc": protorpc.NewCodec, + "application/octet-stream": protorpc.NewCodec, + } +) + +func (protoCodec) Marshal(v interface{}) ([]byte, error) { + return proto.Marshal(v.(proto.Message)) +} + +func (protoCodec) Unmarshal(data []byte, v interface{}) error { + return proto.Unmarshal(data, v.(proto.Message)) +} + +func (protoCodec) Name() string { + return "proto" +} + +func (jsonCodec) Marshal(v interface{}) ([]byte, error) { + return json.Marshal(v) +} + +func (jsonCodec) Unmarshal(data []byte, v interface{}) error { + return json.Unmarshal(data, v) +} + +func (jsonCodec) Name() string { + return "json" +} + +func (bytesCodec) Marshal(v interface{}) ([]byte, error) { + b, ok := v.(*[]byte) + if !ok { + return nil, fmt.Errorf("failed to marshal: %v is not type of *[]byte", v) + } + return *b, nil +} + +func (bytesCodec) Unmarshal(data []byte, v interface{}) error { + b, ok := v.(*[]byte) + if !ok { + return fmt.Errorf("failed to unmarshal: %v is not type of *[]byte", v) + } + *b = data + return nil +} + +func (bytesCodec) Name() string { + return "bytes" +} diff --git a/server/grpc/debug.go b/server/grpc/debug.go new file mode 100644 index 00000000..5f235346 --- /dev/null +++ b/server/grpc/debug.go @@ -0,0 +1,15 @@ +package grpc + +import ( + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/server/debug" +) + +// We use this to wrap any debug handlers so we preserve the signature Debug.{Method} +type Debug struct { + debug.DebugHandler +} + +func registerDebugHandler(s server.Server) { + s.Handle(s.NewHandler(&Debug{s.Options().DebugHandler}, server.InternalHandler(true))) +} diff --git a/server/grpc/error.go b/server/grpc/error.go new file mode 100644 index 00000000..4001622d --- /dev/null +++ b/server/grpc/error.go @@ -0,0 +1,42 @@ +package grpc + +import ( + "net/http" + + "github.com/micro/go-micro/errors" + "google.golang.org/grpc/codes" +) + +func microError(err *errors.Error) codes.Code { + switch err { + case nil: + return codes.OK + } + + switch err.Code { + case http.StatusOK: + return codes.OK + case http.StatusBadRequest: + return codes.InvalidArgument + case http.StatusRequestTimeout: + return codes.DeadlineExceeded + case http.StatusNotFound: + return codes.NotFound + case http.StatusConflict: + return codes.AlreadyExists + case http.StatusForbidden: + return codes.PermissionDenied + case http.StatusUnauthorized: + return codes.Unauthenticated + case http.StatusPreconditionFailed: + return codes.FailedPrecondition + case http.StatusNotImplemented: + return codes.Unimplemented + case http.StatusInternalServerError: + return codes.Internal + case http.StatusServiceUnavailable: + return codes.Unavailable + } + + return codes.Unknown +} diff --git a/server/grpc/extractor.go b/server/grpc/extractor.go new file mode 100644 index 00000000..89c00ce9 --- /dev/null +++ b/server/grpc/extractor.go @@ -0,0 +1,120 @@ +package grpc + +import ( + "fmt" + "reflect" + "strings" + + "github.com/micro/go-micro/registry" +) + +func extractValue(v reflect.Type, d int) *registry.Value { + if d == 3 { + return nil + } + if v == nil { + return nil + } + + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + arg := ®istry.Value{ + Name: v.Name(), + Type: v.Name(), + } + + switch v.Kind() { + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + val := extractValue(f.Type, d+1) + if val == nil { + continue + } + + // if we can find a json tag use it + if tags := f.Tag.Get("json"); len(tags) > 0 { + parts := strings.Split(tags, ",") + if parts[0] == "-" || parts[0] == "omitempty" { + continue + } + val.Name = parts[0] + } + + // if there's no name default it + if len(val.Name) == 0 { + val.Name = v.Field(i).Name + } + + arg.Values = append(arg.Values, val) + } + case reflect.Slice: + p := v.Elem() + if p.Kind() == reflect.Ptr { + p = p.Elem() + } + arg.Type = "[]" + p.Name() + val := extractValue(v.Elem(), d+1) + if val != nil { + arg.Values = append(arg.Values, val) + } + } + + return arg +} + +func extractEndpoint(method reflect.Method) *registry.Endpoint { + if method.PkgPath != "" { + return nil + } + + var rspType, reqType reflect.Type + var stream bool + mt := method.Type + + switch mt.NumIn() { + case 3: + reqType = mt.In(1) + rspType = mt.In(2) + case 4: + reqType = mt.In(2) + rspType = mt.In(3) + default: + return nil + } + + // are we dealing with a stream? + switch rspType.Kind() { + case reflect.Func, reflect.Interface: + stream = true + } + + request := extractValue(reqType, 0) + response := extractValue(rspType, 0) + + return ®istry.Endpoint{ + Name: method.Name, + Request: request, + Response: response, + Metadata: map[string]string{ + "stream": fmt.Sprintf("%v", stream), + }, + } +} + +func extractSubValue(typ reflect.Type) *registry.Value { + var reqType reflect.Type + switch typ.NumIn() { + case 1: + reqType = typ.In(0) + case 2: + reqType = typ.In(1) + case 3: + reqType = typ.In(2) + default: + return nil + } + return extractValue(reqType, 0) +} diff --git a/server/grpc/extractor_test.go b/server/grpc/extractor_test.go new file mode 100644 index 00000000..ebbd0e2b --- /dev/null +++ b/server/grpc/extractor_test.go @@ -0,0 +1,65 @@ +package grpc + +import ( + "context" + "reflect" + "testing" + + "github.com/micro/go-micro/registry" +) + +type testHandler struct{} + +type testRequest struct{} + +type testResponse struct{} + +func (t *testHandler) Test(ctx context.Context, req *testRequest, rsp *testResponse) error { + return nil +} + +func TestExtractEndpoint(t *testing.T) { + handler := &testHandler{} + typ := reflect.TypeOf(handler) + + var endpoints []*registry.Endpoint + + for m := 0; m < typ.NumMethod(); m++ { + if e := extractEndpoint(typ.Method(m)); e != nil { + endpoints = append(endpoints, e) + } + } + + if i := len(endpoints); i != 1 { + t.Errorf("Expected 1 endpoint, have %d", i) + } + + if endpoints[0].Name != "Test" { + t.Errorf("Expected handler Test, got %s", endpoints[0].Name) + } + + if endpoints[0].Request == nil { + t.Error("Expected non nil request") + } + + if endpoints[0].Response == nil { + t.Error("Expected non nil request") + } + + if endpoints[0].Request.Name != "testRequest" { + t.Errorf("Expected testRequest got %s", endpoints[0].Request.Name) + } + + if endpoints[0].Response.Name != "testResponse" { + t.Errorf("Expected testResponse got %s", endpoints[0].Response.Name) + } + + if endpoints[0].Request.Type != "testRequest" { + t.Errorf("Expected testRequest type got %s", endpoints[0].Request.Type) + } + + if endpoints[0].Response.Type != "testResponse" { + t.Errorf("Expected testResponse type got %s", endpoints[0].Response.Type) + } + +} diff --git a/server/grpc/grpc.go b/server/grpc/grpc.go new file mode 100644 index 00000000..54197fc1 --- /dev/null +++ b/server/grpc/grpc.go @@ -0,0 +1,731 @@ +// Package grpc provides a grpc server +package grpc + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "reflect" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/errors" + meta "github.com/micro/go-micro/metadata" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/util/addr" + mgrpc "github.com/micro/go-micro/util/grpc" + "github.com/micro/go-micro/util/log" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/encoding" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +var ( + // DefaultMaxMsgSize define maximum message size that server can send + // or receive. Default value is 4MB. + DefaultMaxMsgSize = 1024 * 1024 * 4 +) + +const ( + defaultContentType = "application/grpc" +) + +type grpcServer struct { + rpc *rServer + srv *grpc.Server + exit chan chan error + wg *sync.WaitGroup + + sync.RWMutex + opts server.Options + handlers map[string]server.Handler + subscribers map[*subscriber][]broker.Subscriber + // used for first registration + registered bool +} + +func init() { + encoding.RegisterCodec(jsonCodec{}) + encoding.RegisterCodec(bytesCodec{}) + + cmd.DefaultServers["grpc"] = NewServer +} + +func newGRPCServer(opts ...server.Option) server.Server { + options := newOptions(opts...) + + // create a grpc server + srv := &grpcServer{ + opts: options, + rpc: &rServer{ + serviceMap: make(map[string]*service), + }, + handlers: make(map[string]server.Handler), + subscribers: make(map[*subscriber][]broker.Subscriber), + exit: make(chan chan error), + wg: wait(options.Context), + } + + // configure the grpc server + srv.configure() + + return srv +} + +func (g *grpcServer) configure(opts ...server.Option) { + // Don't reprocess where there's no config + if len(opts) == 0 && g.srv != nil { + return + } + + for _, o := range opts { + o(&g.opts) + } + + maxMsgSize := g.getMaxMsgSize() + + gopts := []grpc.ServerOption{ + grpc.MaxRecvMsgSize(maxMsgSize), + grpc.MaxSendMsgSize(maxMsgSize), + grpc.UnknownServiceHandler(g.handler), + } + + if creds := g.getCredentials(); creds != nil { + gopts = append(gopts, grpc.Creds(creds)) + } + + if opts := g.getGrpcOptions(); opts != nil { + gopts = append(gopts, opts...) + } + + g.srv = grpc.NewServer(gopts...) +} + +func (g *grpcServer) getMaxMsgSize() int { + if g.opts.Context == nil { + return DefaultMaxMsgSize + } + s, ok := g.opts.Context.Value(maxMsgSizeKey{}).(int) + if !ok { + return DefaultMaxMsgSize + } + return s +} + +func (g *grpcServer) getCredentials() credentials.TransportCredentials { + if g.opts.Context != nil { + if v := g.opts.Context.Value(tlsAuth{}); v != nil { + tls := v.(*tls.Config) + return credentials.NewTLS(tls) + } + } + return nil +} + +func (g *grpcServer) getGrpcOptions() []grpc.ServerOption { + if g.opts.Context == nil { + return nil + } + + v := g.opts.Context.Value(grpcOptions{}) + + if v == nil { + return nil + } + + opts, ok := v.([]grpc.ServerOption) + + if !ok { + return nil + } + + return opts +} + +func (g *grpcServer) handler(srv interface{}, stream grpc.ServerStream) error { + if g.wg != nil { + g.wg.Add(1) + defer g.wg.Done() + } + + fullMethod, ok := grpc.MethodFromServerStream(stream) + if !ok { + return grpc.Errorf(codes.Internal, "method does not exist in context") + } + + serviceName, methodName, err := mgrpc.ServiceMethod(fullMethod) + if err != nil { + return status.New(codes.InvalidArgument, err.Error()).Err() + } + + g.rpc.mu.Lock() + service := g.rpc.serviceMap[serviceName] + g.rpc.mu.Unlock() + + if service == nil { + return status.New(codes.Unimplemented, fmt.Sprintf("unknown service %v", service)).Err() + } + + mtype := service.method[methodName] + if mtype == nil { + return status.New(codes.Unimplemented, fmt.Sprintf("unknown service %v", service)).Err() + } + + // get grpc metadata + gmd, ok := metadata.FromIncomingContext(stream.Context()) + if !ok { + gmd = metadata.MD{} + } + + // copy the metadata to go-micro.metadata + md := meta.Metadata{} + for k, v := range gmd { + md[k] = strings.Join(v, ", ") + } + + // timeout for server deadline + to := md["timeout"] + + // get content type + ct := defaultContentType + if ctype, ok := md["x-content-type"]; ok { + ct = ctype + } + + delete(md, "x-content-type") + delete(md, "timeout") + + // create new context + ctx := meta.NewContext(stream.Context(), md) + + // set the timeout if we have it + if len(to) > 0 { + if n, err := strconv.ParseUint(to, 10, 64); err == nil { + ctx, _ = context.WithTimeout(ctx, time.Duration(n)) + } + } + + // process unary + if !mtype.stream { + return g.processRequest(stream, service, mtype, ct, ctx) + } + + // process stream + return g.processStream(stream, service, mtype, ct, ctx) +} + +func (g *grpcServer) processRequest(stream grpc.ServerStream, service *service, mtype *methodType, ct string, ctx context.Context) error { + for { + var argv, replyv reflect.Value + + // Decode the argument value. + argIsValue := false // if true, need to indirect before calling. + if mtype.ArgType.Kind() == reflect.Ptr { + argv = reflect.New(mtype.ArgType.Elem()) + } else { + argv = reflect.New(mtype.ArgType) + argIsValue = true + } + + // Unmarshal request + if err := stream.RecvMsg(argv.Interface()); err != nil { + return err + } + + if argIsValue { + argv = argv.Elem() + } + + // reply value + replyv = reflect.New(mtype.ReplyType.Elem()) + + function := mtype.method.Func + var returnValues []reflect.Value + + cc, err := g.newGRPCCodec(ct) + if err != nil { + return errors.InternalServerError("go.micro.server", err.Error()) + } + b, err := cc.Marshal(argv.Interface()) + if err != nil { + return err + } + + // create a client.Request + r := &rpcRequest{ + service: g.opts.Name, + contentType: ct, + method: fmt.Sprintf("%s.%s", service.name, mtype.method.Name), + body: b, + payload: argv.Interface(), + } + + // define the handler func + fn := func(ctx context.Context, req server.Request, rsp interface{}) error { + returnValues = function.Call([]reflect.Value{service.rcvr, mtype.prepareContext(ctx), reflect.ValueOf(argv.Interface()), reflect.ValueOf(rsp)}) + + // The return value for the method is an error. + if err := returnValues[0].Interface(); err != nil { + return err.(error) + } + + return nil + } + + // wrap the handler func + for i := len(g.opts.HdlrWrappers); i > 0; i-- { + fn = g.opts.HdlrWrappers[i-1](fn) + } + + statusCode := codes.OK + statusDesc := "" + + // execute the handler + if appErr := fn(ctx, r, replyv.Interface()); appErr != nil { + if err, ok := appErr.(*rpcError); ok { + statusCode = err.code + statusDesc = err.desc + } else if err, ok := appErr.(*errors.Error); ok { + statusCode = microError(err) + statusDesc = appErr.Error() + } else { + statusCode = convertCode(appErr) + statusDesc = appErr.Error() + } + return status.New(statusCode, statusDesc).Err() + } + if err := stream.SendMsg(replyv.Interface()); err != nil { + return err + } + return status.New(statusCode, statusDesc).Err() + } +} + +func (g *grpcServer) processStream(stream grpc.ServerStream, service *service, mtype *methodType, ct string, ctx context.Context) error { + opts := g.opts + + r := &rpcRequest{ + service: opts.Name, + contentType: ct, + method: fmt.Sprintf("%s.%s", service.name, mtype.method.Name), + stream: true, + } + + ss := &rpcStream{ + request: r, + s: stream, + } + + function := mtype.method.Func + var returnValues []reflect.Value + + // Invoke the method, providing a new value for the reply. + fn := func(ctx context.Context, req server.Request, stream interface{}) error { + returnValues = function.Call([]reflect.Value{service.rcvr, mtype.prepareContext(ctx), reflect.ValueOf(stream)}) + if err := returnValues[0].Interface(); err != nil { + return err.(error) + } + + return nil + } + + for i := len(opts.HdlrWrappers); i > 0; i-- { + fn = opts.HdlrWrappers[i-1](fn) + } + + statusCode := codes.OK + statusDesc := "" + + appErr := fn(ctx, r, ss) + if appErr != nil { + if err, ok := appErr.(*rpcError); ok { + statusCode = err.code + statusDesc = err.desc + } else if err, ok := appErr.(*errors.Error); ok { + statusCode = microError(err) + statusDesc = appErr.Error() + } else { + statusCode = convertCode(appErr) + statusDesc = appErr.Error() + } + } + + return status.New(statusCode, statusDesc).Err() +} + +func (g *grpcServer) newGRPCCodec(contentType string) (encoding.Codec, error) { + codecs := make(map[string]encoding.Codec) + if g.opts.Context != nil { + if v := g.opts.Context.Value(codecsKey{}); v != nil { + codecs = v.(map[string]encoding.Codec) + } + } + if c, ok := codecs[contentType]; ok { + return c, nil + } + if c, ok := defaultGRPCCodecs[contentType]; ok { + return c, nil + } + return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) +} + +func (g *grpcServer) newCodec(contentType string) (codec.NewCodec, error) { + if cf, ok := g.opts.Codecs[contentType]; ok { + return cf, nil + } + if cf, ok := defaultRPCCodecs[contentType]; ok { + return cf, nil + } + return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) +} + +func (g *grpcServer) Options() server.Options { + opts := g.opts + return opts +} + +func (g *grpcServer) Init(opts ...server.Option) error { + g.configure(opts...) + return nil +} + +func (g *grpcServer) NewHandler(h interface{}, opts ...server.HandlerOption) server.Handler { + return newRpcHandler(h, opts...) +} + +func (g *grpcServer) Handle(h server.Handler) error { + if err := g.rpc.register(h.Handler()); err != nil { + return err + } + + g.handlers[h.Name()] = h + return nil +} + +func (g *grpcServer) NewSubscriber(topic string, sb interface{}, opts ...server.SubscriberOption) server.Subscriber { + return newSubscriber(topic, sb, opts...) +} + +func (g *grpcServer) Subscribe(sb server.Subscriber) error { + sub, ok := sb.(*subscriber) + if !ok { + return fmt.Errorf("invalid subscriber: expected *subscriber") + } + if len(sub.handlers) == 0 { + return fmt.Errorf("invalid subscriber: no handler functions") + } + + if err := validateSubscriber(sb); err != nil { + return err + } + + g.Lock() + + _, ok = g.subscribers[sub] + if ok { + return fmt.Errorf("subscriber %v already exists", sub) + } + g.subscribers[sub] = nil + g.Unlock() + return nil +} + +func (g *grpcServer) Register() error { + // parse address for host, port + config := g.opts + var advt, host string + var port int + + // check the advertise address first + // if it exists then use it, otherwise + // use the address + if len(config.Advertise) > 0 { + advt = config.Advertise + } else { + advt = config.Address + } + + parts := strings.Split(advt, ":") + if len(parts) > 1 { + host = strings.Join(parts[:len(parts)-1], ":") + port, _ = strconv.Atoi(parts[len(parts)-1]) + } else { + host = parts[0] + } + + addr, err := addr.Extract(host) + if err != nil { + return err + } + + // register service + node := ®istry.Node{ + Id: config.Name + "-" + config.Id, + Address: addr, + Port: port, + Metadata: config.Metadata, + } + + node.Metadata["broker"] = config.Broker.String() + node.Metadata["registry"] = config.Registry.String() + node.Metadata["server"] = g.String() + node.Metadata["transport"] = g.String() + // node.Metadata["transport"] = config.Transport.String() + + g.RLock() + // Maps are ordered randomly, sort the keys for consistency + var handlerList []string + for n, e := range g.handlers { + // Only advertise non internal handlers + if !e.Options().Internal { + handlerList = append(handlerList, n) + } + } + sort.Strings(handlerList) + + var subscriberList []*subscriber + for e := range g.subscribers { + // Only advertise non internal subscribers + if !e.Options().Internal { + subscriberList = append(subscriberList, e) + } + } + sort.Slice(subscriberList, func(i, j int) bool { + return subscriberList[i].topic > subscriberList[j].topic + }) + + var endpoints []*registry.Endpoint + for _, n := range handlerList { + endpoints = append(endpoints, g.handlers[n].Endpoints()...) + } + for _, e := range subscriberList { + endpoints = append(endpoints, e.Endpoints()...) + } + g.RUnlock() + + service := ®istry.Service{ + Name: config.Name, + Version: config.Version, + Nodes: []*registry.Node{node}, + Endpoints: endpoints, + } + + g.Lock() + registered := g.registered + g.Unlock() + + if !registered { + log.Logf("Registering node: %s", node.Id) + } + + // create registry options + rOpts := []registry.RegisterOption{registry.RegisterTTL(config.RegisterTTL)} + + if err := config.Registry.Register(service, rOpts...); err != nil { + return err + } + + // already registered? don't need to register subscribers + if registered { + return nil + } + + g.Lock() + defer g.Unlock() + + g.registered = true + + for sb, _ := range g.subscribers { + handler := g.createSubHandler(sb, g.opts) + var opts []broker.SubscribeOption + if queue := sb.Options().Queue; len(queue) > 0 { + opts = append(opts, broker.Queue(queue)) + } + + if !sb.Options().AutoAck { + opts = append(opts, broker.DisableAutoAck()) + } + + sub, err := config.Broker.Subscribe(sb.Topic(), handler, opts...) + if err != nil { + return err + } + g.subscribers[sb] = []broker.Subscriber{sub} + } + + return nil +} + +func (g *grpcServer) Deregister() error { + config := g.opts + var advt, host string + var port int + + // check the advertise address first + // if it exists then use it, otherwise + // use the address + if len(config.Advertise) > 0 { + advt = config.Advertise + } else { + advt = config.Address + } + + parts := strings.Split(advt, ":") + if len(parts) > 1 { + host = strings.Join(parts[:len(parts)-1], ":") + port, _ = strconv.Atoi(parts[len(parts)-1]) + } else { + host = parts[0] + } + + addr, err := addr.Extract(host) + if err != nil { + return err + } + + node := ®istry.Node{ + Id: config.Name + "-" + config.Id, + Address: addr, + Port: port, + } + + service := ®istry.Service{ + Name: config.Name, + Version: config.Version, + Nodes: []*registry.Node{node}, + } + + log.Logf("Deregistering node: %s", node.Id) + if err := config.Registry.Deregister(service); err != nil { + return err + } + + g.Lock() + + if !g.registered { + g.Unlock() + return nil + } + + g.registered = false + + for sb, subs := range g.subscribers { + for _, sub := range subs { + log.Logf("Unsubscribing from topic: %s", sub.Topic()) + sub.Unsubscribe() + } + g.subscribers[sb] = nil + } + + g.Unlock() + return nil +} + +func (g *grpcServer) Start() error { + registerDebugHandler(g) + config := g.opts + + // micro: config.Transport.Listen(config.Address) + ts, err := net.Listen("tcp", config.Address) + if err != nil { + return err + } + + log.Logf("Server [grpc] Listening on %s", ts.Addr().String()) + g.Lock() + g.opts.Address = ts.Addr().String() + g.Unlock() + + // connect to the broker + if err := config.Broker.Connect(); err != nil { + return err + } + + log.Logf("Broker [%s] Listening on %s", config.Broker.String(), config.Broker.Address()) + + // announce self to the world + if err := g.Register(); err != nil { + log.Log("Server register error: ", err) + } + + // micro: go ts.Accept(s.accept) + go func() { + if err := g.srv.Serve(ts); err != nil { + log.Log("gRPC Server start error: ", err) + } + }() + + go func() { + t := new(time.Ticker) + + // only process if it exists + if g.opts.RegisterInterval > time.Duration(0) { + // new ticker + t = time.NewTicker(g.opts.RegisterInterval) + } + + // return error chan + var ch chan error + + Loop: + for { + select { + // register self on interval + case <-t.C: + if err := g.Register(); err != nil { + log.Log("Server register error: ", err) + } + // wait for exit + case ch = <-g.exit: + break Loop + } + } + + // deregister self + if err := g.Deregister(); err != nil { + log.Log("Server deregister error: ", err) + } + + // wait for waitgroup + if g.wg != nil { + g.wg.Wait() + } + + // stop the grpc server + g.srv.GracefulStop() + + // close transport + ch <- nil + + // disconnect broker + config.Broker.Disconnect() + }() + + return nil +} + +func (g *grpcServer) Stop() error { + ch := make(chan error) + g.exit <- ch + return <-ch +} + +func (g *grpcServer) String() string { + return "grpc" +} + +func NewServer(opts ...server.Option) server.Server { + return newGRPCServer(opts...) +} diff --git a/server/grpc/grpc_test.go b/server/grpc/grpc_test.go new file mode 100644 index 00000000..d489e034 --- /dev/null +++ b/server/grpc/grpc_test.go @@ -0,0 +1,66 @@ +package grpc + +import ( + "context" + "testing" + + "github.com/micro/go-micro/registry/memory" + "github.com/micro/go-micro/server" + "google.golang.org/grpc" + + pb "github.com/micro/examples/greeter/srv/proto/hello" +) + +// server is used to implement helloworld.GreeterServer. +type sayServer struct{} + +// SayHello implements helloworld.GreeterServer +func (s *sayServer) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error { + rsp.Msg = "Hello " + req.Name + return nil +} + +func TestGRPCServer(t *testing.T) { + r := memory.NewRegistry() + s := NewServer( + server.Name("foo"), + server.Registry(r), + ) + + pb.RegisterSayHandler(s, &sayServer{}) + + if err := s.Start(); err != nil { + t.Fatalf("failed to start: %v", err) + } + + // check registration + services, err := r.GetService("foo") + if err != nil || len(services) == 0 { + t.Fatalf("failed to get service: %v # %d", err, len(services)) + } + + defer func() { + if err := s.Stop(); err != nil { + t.Fatalf("failed to stop: %v", err) + } + }() + + cc, err := grpc.Dial(s.Options().Address, grpc.WithInsecure()) + if err != nil { + t.Fatalf("failed to dial server: %v", err) + } + + testMethods := []string{"/helloworld.Say/Hello", "/greeter.helloworld.Say/Hello"} + + for _, method := range testMethods { + rsp := pb.Response{} + + if err := cc.Invoke(context.Background(), method, &pb.Request{Name: "John"}, &rsp); err != nil { + t.Fatalf("error calling server: %v", err) + } + + if rsp.Msg != "Hello John" { + t.Fatalf("Got unexpected response %v", rsp.Msg) + } + } +} diff --git a/server/grpc/handler.go b/server/grpc/handler.go new file mode 100644 index 00000000..f41a1fe9 --- /dev/null +++ b/server/grpc/handler.go @@ -0,0 +1,66 @@ +package grpc + +import ( + "reflect" + + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/server" +) + +type rpcHandler struct { + name string + handler interface{} + endpoints []*registry.Endpoint + opts server.HandlerOptions +} + +func newRpcHandler(handler interface{}, opts ...server.HandlerOption) server.Handler { + options := server.HandlerOptions{ + Metadata: make(map[string]map[string]string), + } + + for _, o := range opts { + o(&options) + } + + typ := reflect.TypeOf(handler) + hdlr := reflect.ValueOf(handler) + name := reflect.Indirect(hdlr).Type().Name() + + var endpoints []*registry.Endpoint + + for m := 0; m < typ.NumMethod(); m++ { + if e := extractEndpoint(typ.Method(m)); e != nil { + e.Name = name + "." + e.Name + + for k, v := range options.Metadata[e.Name] { + e.Metadata[k] = v + } + + endpoints = append(endpoints, e) + } + } + + return &rpcHandler{ + name: name, + handler: handler, + endpoints: endpoints, + opts: options, + } +} + +func (r *rpcHandler) Name() string { + return r.name +} + +func (r *rpcHandler) Handler() interface{} { + return r.handler +} + +func (r *rpcHandler) Endpoints() []*registry.Endpoint { + return r.endpoints +} + +func (r *rpcHandler) Options() server.HandlerOptions { + return r.opts +} diff --git a/server/grpc/options.go b/server/grpc/options.go new file mode 100644 index 00000000..65d82fcc --- /dev/null +++ b/server/grpc/options.go @@ -0,0 +1,113 @@ +package grpc + +import ( + "context" + "crypto/tls" + + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/server/debug" + "github.com/micro/go-micro/transport" + "google.golang.org/grpc" + "google.golang.org/grpc/encoding" +) + +type codecsKey struct{} +type tlsAuth struct{} +type maxMsgSizeKey struct{} +type grpcOptions struct{} + +// gRPC Codec to be used to encode/decode requests for a given content type +func Codec(contentType string, c encoding.Codec) server.Option { + return func(o *server.Options) { + codecs := make(map[string]encoding.Codec) + if o.Context == nil { + o.Context = context.Background() + } + if v := o.Context.Value(codecsKey{}); v != nil { + codecs = v.(map[string]encoding.Codec) + } + codecs[contentType] = c + o.Context = context.WithValue(o.Context, codecsKey{}, codecs) + } +} + +// AuthTLS should be used to setup a secure authentication using TLS +func AuthTLS(t *tls.Config) server.Option { + return func(o *server.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, tlsAuth{}, t) + } +} + +// Options to be used to configure gRPC options +func Options(opts ...grpc.ServerOption) server.Option { + return func(o *server.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, grpcOptions{}, opts) + } +} + +// +// MaxMsgSize set the maximum message in bytes the server can receive and +// send. Default maximum message size is 4 MB. +// +func MaxMsgSize(s int) server.Option { + return func(o *server.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, maxMsgSizeKey{}, s) + } +} + +func newOptions(opt ...server.Option) server.Options { + opts := server.Options{ + Codecs: make(map[string]codec.NewCodec), + Metadata: map[string]string{}, + } + + for _, o := range opt { + o(&opts) + } + + if opts.Broker == nil { + opts.Broker = broker.DefaultBroker + } + + if opts.Registry == nil { + opts.Registry = registry.DefaultRegistry + } + + if opts.Transport == nil { + opts.Transport = transport.DefaultTransport + } + + if opts.DebugHandler == nil { + opts.DebugHandler = debug.DefaultDebugHandler + } + + if len(opts.Address) == 0 { + opts.Address = server.DefaultAddress + } + + if len(opts.Name) == 0 { + opts.Name = server.DefaultName + } + + if len(opts.Id) == 0 { + opts.Id = server.DefaultId + } + + if len(opts.Version) == 0 { + opts.Version = server.DefaultVersion + } + + return opts +} diff --git a/server/grpc/request.go b/server/grpc/request.go new file mode 100644 index 00000000..951c1a19 --- /dev/null +++ b/server/grpc/request.go @@ -0,0 +1,70 @@ +package grpc + +import ( + "github.com/micro/go-micro/codec" +) + +type rpcRequest struct { + service string + method string + contentType string + codec codec.Codec + header map[string]string + body []byte + stream bool + payload interface{} +} + +type rpcMessage struct { + topic string + contentType string + payload interface{} +} + +func (r *rpcRequest) ContentType() string { + return r.contentType +} + +func (r *rpcRequest) Service() string { + return r.service +} + +func (r *rpcRequest) Method() string { + return r.method +} + +func (r *rpcRequest) Endpoint() string { + return r.method +} + +func (r *rpcRequest) Codec() codec.Reader { + return r.codec +} + +func (r *rpcRequest) Header() map[string]string { + return r.header +} + +func (r *rpcRequest) Read() ([]byte, error) { + return r.body, nil +} + +func (r *rpcRequest) Stream() bool { + return r.stream +} + +func (r *rpcRequest) Body() interface{} { + return r.payload +} + +func (r *rpcMessage) ContentType() string { + return r.contentType +} + +func (r *rpcMessage) Topic() string { + return r.topic +} + +func (r *rpcMessage) Payload() interface{} { + return r.payload +} diff --git a/server/grpc/server.go b/server/grpc/server.go new file mode 100644 index 00000000..140d7e5b --- /dev/null +++ b/server/grpc/server.go @@ -0,0 +1,180 @@ +package grpc + +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Meh, we need to get rid of this shit + +import ( + "context" + "errors" + "reflect" + "sync" + "unicode" + "unicode/utf8" + + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/util/log" +) + +var ( + // Precompute the reflect type for error. Can't use error directly + // because Typeof takes an empty interface value. This is annoying. + typeOfError = reflect.TypeOf((*error)(nil)).Elem() +) + +type methodType struct { + method reflect.Method + ArgType reflect.Type + ReplyType reflect.Type + ContextType reflect.Type + stream bool +} + +type service struct { + name string // name of service + rcvr reflect.Value // receiver of methods for the service + typ reflect.Type // type of the receiver + method map[string]*methodType // registered methods +} + +// server represents an RPC Server. +type rServer struct { + mu sync.Mutex // protects the serviceMap + serviceMap map[string]*service +} + +// Is this an exported - upper case - name? +func isExported(name string) bool { + rune, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(rune) +} + +// Is this type exported or a builtin? +func isExportedOrBuiltinType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + // PkgPath will be non-empty even for an exported type, + // so we need to check the type name as well. + return isExported(t.Name()) || t.PkgPath() == "" +} + +// prepareEndpoint() returns a methodType for the provided method or nil +// in case if the method was unsuitable. +func prepareEndpoint(method reflect.Method) *methodType { + mtype := method.Type + mname := method.Name + var replyType, argType, contextType reflect.Type + var stream bool + + // Endpoint() must be exported. + if method.PkgPath != "" { + return nil + } + + switch mtype.NumIn() { + case 3: + // assuming streaming + argType = mtype.In(2) + contextType = mtype.In(1) + stream = true + case 4: + // method that takes a context + argType = mtype.In(2) + replyType = mtype.In(3) + contextType = mtype.In(1) + default: + log.Log("method", mname, "of", mtype, "has wrong number of ins:", mtype.NumIn()) + return nil + } + + if stream { + // check stream type + streamType := reflect.TypeOf((*server.Stream)(nil)).Elem() + if !argType.Implements(streamType) { + log.Log(mname, "argument does not implement Streamer interface:", argType) + return nil + } + } else { + // if not stream check the replyType + + // First arg need not be a pointer. + if !isExportedOrBuiltinType(argType) { + log.Log(mname, "argument type not exported:", argType) + return nil + } + + if replyType.Kind() != reflect.Ptr { + log.Log("method", mname, "reply type not a pointer:", replyType) + return nil + } + + // Reply type must be exported. + if !isExportedOrBuiltinType(replyType) { + log.Log("method", mname, "reply type not exported:", replyType) + return nil + } + } + + // Endpoint() needs one out. + if mtype.NumOut() != 1 { + log.Log("method", mname, "has wrong number of outs:", mtype.NumOut()) + return nil + } + // The return type of the method must be error. + if returnType := mtype.Out(0); returnType != typeOfError { + log.Log("method", mname, "returns", returnType.String(), "not error") + return nil + } + return &methodType{method: method, ArgType: argType, ReplyType: replyType, ContextType: contextType, stream: stream} +} + +func (server *rServer) register(rcvr interface{}) error { + server.mu.Lock() + defer server.mu.Unlock() + if server.serviceMap == nil { + server.serviceMap = make(map[string]*service) + } + s := new(service) + s.typ = reflect.TypeOf(rcvr) + s.rcvr = reflect.ValueOf(rcvr) + sname := reflect.Indirect(s.rcvr).Type().Name() + if sname == "" { + log.Fatal("rpc: no service name for type", s.typ.String()) + } + if !isExported(sname) { + s := "rpc Register: type " + sname + " is not exported" + log.Log(s) + return errors.New(s) + } + if _, present := server.serviceMap[sname]; present { + return errors.New("rpc: service already defined: " + sname) + } + s.name = sname + s.method = make(map[string]*methodType) + + // Install the methods + for m := 0; m < s.typ.NumMethod(); m++ { + method := s.typ.Method(m) + if mt := prepareEndpoint(method); mt != nil { + s.method[method.Name] = mt + } + } + + if len(s.method) == 0 { + s := "rpc Register: type " + sname + " has no exported methods of suitable type" + log.Log(s) + return errors.New(s) + } + server.serviceMap[s.name] = s + return nil +} + +func (m *methodType) prepareContext(ctx context.Context) reflect.Value { + if contextv := reflect.ValueOf(ctx); contextv.IsValid() { + return contextv + } + return reflect.Zero(m.ContextType) +} diff --git a/server/grpc/stream.go b/server/grpc/stream.go new file mode 100644 index 00000000..ae31a0bb --- /dev/null +++ b/server/grpc/stream.go @@ -0,0 +1,38 @@ +package grpc + +import ( + "context" + + "github.com/micro/go-micro/server" + "google.golang.org/grpc" +) + +// rpcStream implements a server side Stream. +type rpcStream struct { + s grpc.ServerStream + request server.Request +} + +func (r *rpcStream) Close() error { + return nil +} + +func (r *rpcStream) Error() error { + return nil +} + +func (r *rpcStream) Request() server.Request { + return r.request +} + +func (r *rpcStream) Context() context.Context { + return r.s.Context() +} + +func (r *rpcStream) Send(m interface{}) error { + return r.s.SendMsg(m) +} + +func (r *rpcStream) Recv(m interface{}) error { + return r.s.RecvMsg(m) +} diff --git a/server/grpc/subscriber.go b/server/grpc/subscriber.go new file mode 100644 index 00000000..56cf6db3 --- /dev/null +++ b/server/grpc/subscriber.go @@ -0,0 +1,262 @@ +package grpc + +import ( + "bytes" + "context" + "fmt" + "reflect" + + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/codec" + "github.com/micro/go-micro/metadata" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/server" +) + +const ( + subSig = "func(context.Context, interface{}) error" +) + +type handler struct { + method reflect.Value + reqType reflect.Type + ctxType reflect.Type +} + +type subscriber struct { + topic string + rcvr reflect.Value + typ reflect.Type + subscriber interface{} + handlers []*handler + endpoints []*registry.Endpoint + opts server.SubscriberOptions +} + +func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOption) server.Subscriber { + + options := server.SubscriberOptions{ + AutoAck: true, + } + + for _, o := range opts { + o(&options) + } + + var endpoints []*registry.Endpoint + var handlers []*handler + + if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func { + h := &handler{ + method: reflect.ValueOf(sub), + } + + switch typ.NumIn() { + case 1: + h.reqType = typ.In(0) + case 2: + h.ctxType = typ.In(0) + h.reqType = typ.In(1) + } + + handlers = append(handlers, h) + + endpoints = append(endpoints, ®istry.Endpoint{ + Name: "Func", + Request: extractSubValue(typ), + Metadata: map[string]string{ + "topic": topic, + "subscriber": "true", + }, + }) + } else { + hdlr := reflect.ValueOf(sub) + name := reflect.Indirect(hdlr).Type().Name() + + for m := 0; m < typ.NumMethod(); m++ { + method := typ.Method(m) + h := &handler{ + method: method.Func, + } + + switch method.Type.NumIn() { + case 2: + h.reqType = method.Type.In(1) + case 3: + h.ctxType = method.Type.In(1) + h.reqType = method.Type.In(2) + } + + handlers = append(handlers, h) + + endpoints = append(endpoints, ®istry.Endpoint{ + Name: name + "." + method.Name, + Request: extractSubValue(method.Type), + Metadata: map[string]string{ + "topic": topic, + "subscriber": "true", + }, + }) + } + } + + return &subscriber{ + rcvr: reflect.ValueOf(sub), + typ: reflect.TypeOf(sub), + topic: topic, + subscriber: sub, + handlers: handlers, + endpoints: endpoints, + opts: options, + } +} + +func validateSubscriber(sub server.Subscriber) error { + typ := reflect.TypeOf(sub.Subscriber()) + var argType reflect.Type + + if typ.Kind() == reflect.Func { + name := "Func" + switch typ.NumIn() { + case 2: + argType = typ.In(1) + default: + return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig) + } + if !isExportedOrBuiltinType(argType) { + return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType) + } + if typ.NumOut() != 1 { + return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s", + name, typ.NumOut(), subSig) + } + if returnType := typ.Out(0); returnType != typeOfError { + return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String()) + } + } else { + hdlr := reflect.ValueOf(sub.Subscriber()) + name := reflect.Indirect(hdlr).Type().Name() + + for m := 0; m < typ.NumMethod(); m++ { + method := typ.Method(m) + + switch method.Type.NumIn() { + case 3: + argType = method.Type.In(2) + default: + return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s", + name, method.Name, method.Type.NumIn(), subSig) + } + + if !isExportedOrBuiltinType(argType) { + return fmt.Errorf("%v argument type not exported: %v", name, argType) + } + if method.Type.NumOut() != 1 { + return fmt.Errorf( + "subscriber %v.%v has wrong number of outs: %v require signature %s", + name, method.Name, method.Type.NumOut(), subSig) + } + if returnType := method.Type.Out(0); returnType != typeOfError { + return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String()) + } + } + } + + return nil +} + +func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broker.Handler { + return func(p broker.Publication) error { + msg := p.Message() + ct := msg.Header["Content-Type"] + cf, err := g.newCodec(ct) + if err != nil { + return err + } + + hdr := make(map[string]string) + for k, v := range msg.Header { + hdr[k] = v + } + delete(hdr, "Content-Type") + ctx := metadata.NewContext(context.Background(), hdr) + + for i := 0; i < len(sb.handlers); i++ { + handler := sb.handlers[i] + + var isVal bool + var req reflect.Value + + if handler.reqType.Kind() == reflect.Ptr { + req = reflect.New(handler.reqType.Elem()) + } else { + req = reflect.New(handler.reqType) + isVal = true + } + if isVal { + req = req.Elem() + } + + b := &buffer{bytes.NewBuffer(msg.Body)} + co := cf(b) + defer co.Close() + + if err := co.ReadHeader(&codec.Message{}, codec.Publication); err != nil { + return err + } + + if err := co.ReadBody(req.Interface()); err != nil { + return err + } + + fn := func(ctx context.Context, msg server.Message) error { + var vals []reflect.Value + if sb.typ.Kind() != reflect.Func { + vals = append(vals, sb.rcvr) + } + if handler.ctxType != nil { + vals = append(vals, reflect.ValueOf(ctx)) + } + + vals = append(vals, reflect.ValueOf(msg.Payload())) + + returnValues := handler.method.Call(vals) + if err := returnValues[0].Interface(); err != nil { + return err.(error) + } + return nil + } + + for i := len(opts.SubWrappers); i > 0; i-- { + fn = opts.SubWrappers[i-1](fn) + } + + g.wg.Add(1) + go func() { + defer g.wg.Done() + fn(ctx, &rpcMessage{ + topic: sb.topic, + contentType: ct, + payload: req.Interface(), + }) + }() + } + return nil + } +} + +func (s *subscriber) Topic() string { + return s.topic +} + +func (s *subscriber) Subscriber() interface{} { + return s.subscriber +} + +func (s *subscriber) Endpoints() []*registry.Endpoint { + return s.endpoints +} + +func (s *subscriber) Options() server.SubscriberOptions { + return s.opts +} diff --git a/server/grpc/util.go b/server/grpc/util.go new file mode 100644 index 00000000..05835488 --- /dev/null +++ b/server/grpc/util.go @@ -0,0 +1,60 @@ +package grpc + +import ( + "context" + "fmt" + "io" + "os" + "sync" + + "google.golang.org/grpc/codes" +) + +// rpcError defines the status from an RPC. +type rpcError struct { + code codes.Code + desc string +} + +func (e *rpcError) Error() string { + return fmt.Sprintf("rpc error: code = %d desc = %s", e.code, e.desc) +} + +// convertCode converts a standard Go error into its canonical code. Note that +// this is only used to translate the error returned by the server applications. +func convertCode(err error) codes.Code { + switch err { + case nil: + return codes.OK + case io.EOF: + return codes.OutOfRange + case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF: + return codes.FailedPrecondition + case os.ErrInvalid: + return codes.InvalidArgument + case context.Canceled: + return codes.Canceled + case context.DeadlineExceeded: + return codes.DeadlineExceeded + } + switch { + case os.IsExist(err): + return codes.AlreadyExists + case os.IsNotExist(err): + return codes.NotFound + case os.IsPermission(err): + return codes.PermissionDenied + } + return codes.Unknown +} + +func wait(ctx context.Context) *sync.WaitGroup { + if ctx == nil { + return nil + } + wg, ok := ctx.Value("wait").(*sync.WaitGroup) + if !ok { + return nil + } + return wg +} diff --git a/service/grpc/.travis.yml b/service/grpc/.travis.yml new file mode 100644 index 00000000..96a32173 --- /dev/null +++ b/service/grpc/.travis.yml @@ -0,0 +1,7 @@ +language: go +go: +- 1.10.x +- 1.11.x +notifications: + slack: + secure: aEvhLbhujaGaKSrOokiG3//PaVHTIrc3fBpoRbCRqfZpyq6WREoapJJhF+tIpWWOwaC9GmChbD6aHo/jMUgwKXVyPSaNjiEL87YzUUpL8B2zslNp1rgfTg/LrzthOx3Q1TYwpaAl3to0fuHUVFX4yMeC2vuThq7WSXgMMxFCtbc= diff --git a/service/grpc/README.md b/service/grpc/README.md new file mode 100644 index 00000000..70e750b0 --- /dev/null +++ b/service/grpc/README.md @@ -0,0 +1,36 @@ +# Micro gRPC [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/micro/go-micro/service/grpc?status.svg)](https://godoc.org/github.com/micro/go-micro/service/grpc) [![Travis CI](https://api.travis-ci.org/micro/go-micro/service/grpc.svg?branch=master)](https://travis-ci.org/micro/go-micro/service/grpc) [![Go Report Card](https://goreportcard.com/badge/micro/go-micro/service/grpc)](https://goreportcard.com/report/github.com/micro/go-micro/service/grpc) + +A micro gRPC framework. A simplified experience for building gRPC services. + +## Overview + +**Go gRPC** makes use of [go-micro](https://github.com/micro/go-micro) plugins to create a simpler framework for gRPC development. +It interoperates with standard gRPC services seamlessly, including the [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway). +The go-grpc library uses the go-micro broker, client and server plugins which make use of +[github.com/grpc/grpc-go](https://github.com/grpc/grpc-go) internally. +This means we ignore the go-micro codec and transport but provide a native grpc experience. + + + +## Features + +- **Service Discovery** - We make use of go-micro's registry and selector interfaces to provide pluggable discovery +and client side load balancing. There's no need to dial connections, we'll do everything beneath the covers for you. + +- **PubSub Messaging** - Where gRPC only provides you synchronous communication, **Go gRPC** uses the go-micro broker +to provide asynchronous messaging while using the gRPC protocol. + +- **Micro Ecosystem** - Make use of the existing micro ecosystem of tooling including our api gateway, web dashboard, +command line interface and much more. We're enhancing gRPC with a simplified experience using micro. + +## Examples + +Find an example greeter service in [examples/greeter](https://github.com/micro/go-micro/service/grpc/tree/master/examples/greeter). + +## Getting Started + +See the [docs](https://micro.mu/docs/go-grpc.html) to get started. + +## I18n + +### [中文](README_cn.md) diff --git a/service/grpc/README_cn.md b/service/grpc/README_cn.md new file mode 100644 index 00000000..99cdaf51 --- /dev/null +++ b/service/grpc/README_cn.md @@ -0,0 +1,25 @@ +# Micro gRPC [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/micro/go-micro/service/grpc?status.svg)](https://godoc.org/github.com/micro/go-micro/service/grpc) [![Travis CI](https://api.travis-ci.org/micro/go-micro/service/grpc.svg?branch=master)](https://travis-ci.org/micro/go-micro/service/grpc) [![Go Report Card](https://goreportcard.com/badge/micro/go-micro/service/grpc)](https://goreportcard.com/report/github.com/micro/go-micro/service/grpc) + +Micro gRPC是micro的gRPC框架插件,简化开发基于gRPC的服务。 + +## 概览 + +micro提供有基于Go的gRPC插件[go-micro](https://github.com/micro/go-micro),该插件可以在内部集成gPRC,并与之无缝交互,让开发gRPC更简单,并支持[grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway)。 + +micro有面向gRPC的[客户端](https://github.com/micro/go-plugins/tree/master/client)和[服务端](https://github.com/micro/go-plugins/tree/master/server)插件,go-grpc库调用客户端/服务端插件生成micro需要的gRPC代码,而客户端/服务端插件都是从[github.com/grpc/grpc-go](https://github.com/grpc/grpc-go)扩展而来,也即是说,我们不需要去知道go-micro是如何编解码或传输就可以使用原生的gRPC。 + +## 特性 + +- **服务发现** - go-micro的服务发现基于其[注册](https://github.com/micro/go-plugins/tree/master/registry)与[选择器](https://github.com/micro/go-micro/tree/master/selector)接口,实现了可插拔的服务发现与客户端侧的负载均衡,不需要拨号连接,micro已经把所有都封装好,大家只管用。 + +- **消息发布订阅** - 因为gRPC只提供同步通信机制,而**Go gRPC**使用go-micro的[broker代理](https://github.com/micro/go-micro/tree/master/broker)提供异步消息,broker也是基于gRPC协议。 + +- **Micro生态系统** - Micro生态系统包含工具链中,比如api网关、web管理控制台、CLI命令行接口等等。我们通过使用micro来增强gRPC框架的易用性。 + +## 示例 + +示例请查看[examples/greeter](https://github.com/micro/go-micro/service/grpc/tree/master/examples/greeter)。 + +## 开始使用 + +我们提供相关文档[docs](https://micro.mu/docs/go-grpc_cn.html),以便上手。 \ No newline at end of file diff --git a/service/grpc/examples/greeter/README.md b/service/grpc/examples/greeter/README.md new file mode 100644 index 00000000..725d13e3 --- /dev/null +++ b/service/grpc/examples/greeter/README.md @@ -0,0 +1,64 @@ +# Greeter Service + +An example Go-Micro based gRPC service + +## What's here? + +- **server** - a gRPC greeter service +- **client** - a gRPC client that calls the service once +- **function** - a gRPC greeter function,more about [Function](https://micro.mu/docs/writing-a-go-function.html) +- **gateway** - a grpc-gateway + +## Test Service + +Run Service +``` +$ go run server/main.go --registry=mdns +2016/11/03 18:41:22 Listening on [::]:55194 +2016/11/03 18:41:22 Broker Listening on [::]:55195 +2016/11/03 18:41:22 Registering node: go.micro.srv.greeter-1e200612-a1f5-11e6-8e84-68a86d0d36b6 +``` + +Test Service +``` +$ go run client/main.go --registry=mdns +Hello John +``` + +## Test Function + +Run function + +``` +go run function/main.go --registry=mdns +``` + +Query function + +``` +go run client/main.go --registry=mdns --service_name="go.micro.fnc.greeter" +``` + +## Test Gateway + +Run server with address set + +``` +go run server/main.go --registry=mdns --server_address=localhost:9090 +``` + +Run gateway + +``` +go run gateway/main.go +``` + +Curl gateway + +``` +curl -d '{"name": "john"}' http://localhost:8080/greeter/hello +``` + +## i18n + +### [中文](README_cn.md) \ No newline at end of file diff --git a/service/grpc/examples/greeter/README_cn.md b/service/grpc/examples/greeter/README_cn.md new file mode 100644 index 00000000..9f96ca8e --- /dev/null +++ b/service/grpc/examples/greeter/README_cn.md @@ -0,0 +1,76 @@ +# Greeter 问候示例服务 + +本示例展示基于gRPC的Go-Micro服务 + +## 本目录有 + +- **server** - Greeter的gRPC服务端 +- **client** - gRPC客户端,会调用一次server +- **function** - 演示gRPC Greeter function接口,更多关于Function,请查阅[Function](https://micro.mu/docs/writing-a-go-function_cn.html) +- **gateway** - gRPC网关 + +## 测试服务 + +运行服务 + +``` +$ go run server/main.go --registry=mdns +2016/11/03 18:41:22 Listening on [::]:55194 +2016/11/03 18:41:22 Broker Listening on [::]:55195 +2016/11/03 18:41:22 Registering node: go.micro.srv.greeter-1e200612-a1f5-11e6-8e84-68a86d0d36b6 +``` + +测试 + +``` +$ go run client/main.go --registry=mdns +Hello John +``` + +## 测试 Function + +运行测试 + +``` +go run function/main.go --registry=mdns +``` + +调用服务 + +服务端的Function服务只会执行一次,所以在下面的命令执行且服务端返回请求后,服务端便后退出 + +```bash +$ go run client/main.go --registry=mdns --service_name="go.micro.fnc.greeter" + +# 返回 +Hello John + +# 再次执行 +$ go run client/main.go --registry=mdns --service_name="go.micro.fnc.greeter" + +# 就会报异常,找不到服务 +{"id":"go.micro.client","code":500,"detail":"none available","status":"Internal Server Error"} + +``` + +## 测试网关 + +指定地址再运行服务端: + +``` +go run server/main.go --registry=mdns --server_address=localhost:9090 +``` + +运行网关 + +``` +go run gateway/main.go +``` + +使用curl调用网关 + +``` +curl -d '{"name": "john"}' http://localhost:8080/greeter/hello +# 返回 +{"msg":"Hello john"} +``` \ No newline at end of file diff --git a/service/grpc/examples/greeter/client/main.go b/service/grpc/examples/greeter/client/main.go new file mode 100644 index 00000000..1d3b2bfa --- /dev/null +++ b/service/grpc/examples/greeter/client/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + "fmt" + + "github.com/micro/cli" + "github.com/micro/go-micro" + "github.com/micro/go-micro/service/grpc" + hello "github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello" +) + +var ( + // service to call + serviceName string +) + +func main() { + service := grpc.NewService() + + service.Init( + micro.Flags(cli.StringFlag{ + Name: "service_name", + Value: "go.micro.srv.greeter", + Destination: &serviceName, + }), + ) + + cl := hello.NewSayService(serviceName, service.Client()) + + rsp, err := cl.Hello(context.TODO(), &hello.Request{ + Name: "John", + }) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(rsp.Msg) +} diff --git a/service/grpc/examples/greeter/function/main.go b/service/grpc/examples/greeter/function/main.go new file mode 100644 index 00000000..0089bcc0 --- /dev/null +++ b/service/grpc/examples/greeter/function/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + "log" + + "github.com/micro/go-micro" + "github.com/micro/go-micro/service/grpc" + hello "github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello" +) + +type Say struct{} + +func (s *Say) Hello(ctx context.Context, req *hello.Request, rsp *hello.Response) error { + rsp.Msg = "Hello " + req.Name + return nil +} + +func main() { + fn := grpc.NewFunction( + micro.Name("go.micro.fnc.greeter"), + ) + + fn.Init() + + fn.Handle(new(Say)) + + if err := fn.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/service/grpc/examples/greeter/function/proto/hello/hello.micro.go b/service/grpc/examples/greeter/function/proto/hello/hello.micro.go new file mode 100644 index 00000000..83ec50b7 --- /dev/null +++ b/service/grpc/examples/greeter/function/proto/hello/hello.micro.go @@ -0,0 +1,99 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto + +/* +Package go_micro_srv_greeter is a generated protocol buffer package. + +It is generated from these files: + github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto + +It has these top-level messages: + Request + Response +*/ +package go_micro_srv_greeter + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "context" + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/server" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ client.Option +var _ server.Option + +// Client API for Say service + +type SayService interface { + Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) +} + +type sayService struct { + c client.Client + serviceName string +} + +func NewSayService(serviceName string, c client.Client) SayService { + if c == nil { + c = client.NewClient() + } + if len(serviceName) == 0 { + serviceName = "go.micro.srv.greeter" + } + return &sayService{ + c: c, + serviceName: serviceName, + } +} + +func (c *sayService) Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) { + req := c.c.NewRequest(c.serviceName, "Say.Hello", in) + out := new(Response) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Say service + +type SayHandler interface { + Hello(context.Context, *Request, *Response) error +} + +func RegisterSayHandler(s server.Server, hdlr SayHandler, opts ...server.HandlerOption) { + type say interface { + Hello(ctx context.Context, in *Request, out *Response) error + } + type Say struct { + say + } + h := &sayHandler{hdlr} + s.Handle(s.NewHandler(&Say{h}, opts...)) +} + +type sayHandler struct { + SayHandler +} + +func (h *sayHandler) Hello(ctx context.Context, in *Request, out *Response) error { + return h.SayHandler.Hello(ctx, in, out) +} diff --git a/service/grpc/examples/greeter/function/proto/hello/hello.pb.go b/service/grpc/examples/greeter/function/proto/hello/hello.pb.go new file mode 100644 index 00000000..17b61162 --- /dev/null +++ b/service/grpc/examples/greeter/function/proto/hello/hello.pb.go @@ -0,0 +1,163 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto + +/* +Package go_micro_srv_greeter is a generated protocol buffer package. + +It is generated from these files: + github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto + +It has these top-level messages: + Request + Response +*/ +package go_micro_srv_greeter + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Request struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *Request) Reset() { *m = Request{} } +func (m *Request) String() string { return proto.CompactTextString(m) } +func (*Request) ProtoMessage() {} +func (*Request) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Request) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type Response struct { + Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} +func (*Response) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *Response) GetMsg() string { + if m != nil { + return m.Msg + } + return "" +} + +func init() { + proto.RegisterType((*Request)(nil), "go.micro.srv.greeter.Request") + proto.RegisterType((*Response)(nil), "go.micro.srv.greeter.Response") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Say service + +type SayClient interface { + Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) +} + +type sayClient struct { + cc *grpc.ClientConn +} + +func NewSayClient(cc *grpc.ClientConn) SayClient { + return &sayClient{cc} +} + +func (c *sayClient) Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := grpc.Invoke(ctx, "/go.micro.srv.greeter.Say/Hello", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Say service + +type SayServer interface { + Hello(context.Context, *Request) (*Response, error) +} + +func RegisterSayServer(s *grpc.Server, srv SayServer) { + s.RegisterService(&_Say_serviceDesc, srv) +} + +func _Say_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SayServer).Hello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.srv.greeter.Say/Hello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SayServer).Hello(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +var _Say_serviceDesc = grpc.ServiceDesc{ + ServiceName: "go.micro.srv.greeter.Say", + HandlerType: (*SayServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Hello", + Handler: _Say_Hello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto", +} + +func init() { + proto.RegisterFile("github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto", fileDescriptor0) +} + +var fileDescriptor0 = []byte{ + // 187 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x8e, 0x4d, 0x0a, 0xc2, 0x30, + 0x10, 0x85, 0x2d, 0xf5, 0x37, 0x2b, 0x09, 0x2e, 0x44, 0xac, 0x48, 0x57, 0x6e, 0x4c, 0x40, 0x2f, + 0x51, 0xdc, 0x08, 0xf5, 0x04, 0x6d, 0x18, 0xd3, 0x42, 0x93, 0x89, 0x49, 0x2a, 0x7a, 0x7b, 0x69, + 0xcc, 0x52, 0x37, 0xc3, 0x63, 0x3e, 0x66, 0xbe, 0x47, 0x2e, 0xb2, 0xf5, 0x4d, 0x5f, 0x33, 0x81, + 0x8a, 0xab, 0x56, 0x58, 0xe4, 0x12, 0x8f, 0xd2, 0x1a, 0xc1, 0xe1, 0x55, 0x29, 0xd3, 0x81, 0xe3, + 0xd2, 0x02, 0x78, 0xb0, 0xfc, 0xde, 0x6b, 0xe1, 0x5b, 0xd4, 0xdc, 0x58, 0xf4, 0xc8, 0x1b, 0xe8, + 0xba, 0x38, 0x59, 0xd8, 0xd0, 0x95, 0x44, 0x16, 0x7e, 0x30, 0x67, 0x9f, 0x2c, 0x9e, 0xe5, 0x19, + 0x99, 0x95, 0xf0, 0xe8, 0xc1, 0x79, 0x4a, 0xc9, 0x58, 0x57, 0x0a, 0xd6, 0xc9, 0x3e, 0x39, 0x2c, + 0xca, 0x90, 0xf3, 0x2d, 0x99, 0x97, 0xe0, 0x0c, 0x6a, 0x07, 0x74, 0x49, 0x52, 0xe5, 0x64, 0xc4, + 0x43, 0x3c, 0x5d, 0x49, 0x7a, 0xab, 0xde, 0xb4, 0x20, 0x93, 0x62, 0x10, 0xd1, 0x8c, 0xfd, 0x72, + 0xb0, 0x28, 0xd8, 0xec, 0xfe, 0xe1, 0xaf, 0x20, 0x1f, 0xd5, 0xd3, 0x50, 0xf5, 0xfc, 0x09, 0x00, + 0x00, 0xff, 0xff, 0x79, 0x62, 0x76, 0xe6, 0xf8, 0x00, 0x00, 0x00, +} diff --git a/service/grpc/examples/greeter/function/proto/hello/hello.proto b/service/grpc/examples/greeter/function/proto/hello/hello.proto new file mode 100644 index 00000000..a7c28a44 --- /dev/null +++ b/service/grpc/examples/greeter/function/proto/hello/hello.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package go.micro.srv.greeter; + +service Say { + rpc Hello(Request) returns (Response) {} +} + +message Request { + string name = 1; +} + +message Response { + string msg = 1; +} diff --git a/service/grpc/examples/greeter/gateway/README.md b/service/grpc/examples/greeter/gateway/README.md new file mode 100644 index 00000000..9e1a9e3f --- /dev/null +++ b/service/grpc/examples/greeter/gateway/README.md @@ -0,0 +1,29 @@ +# GRPC Gateway + +This directory contains a grpc gateway generated using [grpc-ecosystem/grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway). + +Services written with [micro/go-micro/service/grpc](https://github.com/micro/go-micro/service/grpc) are fully compatible with the grpc-gateway and any other +grpc services. + +Go to [grpc-ecosystem/grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) for details on how to generate gateways. We +have generated the gateway from the same proto as the greeter server but with additional options for the gateway. + +## Usage + +Run the go.micro.srv.greeter service + +``` +go run ../server/main.go --server_address=localhost:9090 +``` + +Run the gateway + +``` +go run main.go +``` + +Curl your request at the gateway (localhost:8080) + +``` +curl -d '{"name": "john"}' http://localhost:8080/greeter/hello +``` diff --git a/service/grpc/examples/greeter/gateway/main.go b/service/grpc/examples/greeter/gateway/main.go new file mode 100644 index 00000000..96f83a2b --- /dev/null +++ b/service/grpc/examples/greeter/gateway/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "flag" + "net/http" + + "github.com/golang/glog" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc" + + hello "github.com/micro/examples/grpc/gateway/proto/hello" +) + +var ( + // the go.micro.srv.greeter address + endpoint = flag.String("endpoint", "localhost:9090", "go.micro.srv.greeter address") +) + +func run() error { + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + mux := runtime.NewServeMux() + opts := []grpc.DialOption{grpc.WithInsecure()} + + err := hello.RegisterSayHandlerFromEndpoint(ctx, mux, *endpoint, opts) + if err != nil { + return err + } + + return http.ListenAndServe(":8080", mux) +} + +func main() { + flag.Parse() + + defer glog.Flush() + + if err := run(); err != nil { + glog.Fatal(err) + } +} diff --git a/service/grpc/examples/greeter/gateway/proto/hello/hello.pb.go b/service/grpc/examples/greeter/gateway/proto/hello/hello.pb.go new file mode 100644 index 00000000..ae944de2 --- /dev/null +++ b/service/grpc/examples/greeter/gateway/proto/hello/hello.pb.go @@ -0,0 +1,161 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hello.proto + +/* +Package greeter is a generated protocol buffer package. + +It is generated from these files: + hello.proto + +It has these top-level messages: + Request + Response +*/ +package greeter + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "google.golang.org/genproto/googleapis/api/annotations" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Request struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *Request) Reset() { *m = Request{} } +func (m *Request) String() string { return proto.CompactTextString(m) } +func (*Request) ProtoMessage() {} +func (*Request) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Request) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type Response struct { + Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} +func (*Response) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *Response) GetMsg() string { + if m != nil { + return m.Msg + } + return "" +} + +func init() { + proto.RegisterType((*Request)(nil), "greeter.Request") + proto.RegisterType((*Response)(nil), "greeter.Response") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Say service + +type SayClient interface { + Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) +} + +type sayClient struct { + cc *grpc.ClientConn +} + +func NewSayClient(cc *grpc.ClientConn) SayClient { + return &sayClient{cc} +} + +func (c *sayClient) Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := grpc.Invoke(ctx, "/greeter.Say/Hello", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Say service + +type SayServer interface { + Hello(context.Context, *Request) (*Response, error) +} + +func RegisterSayServer(s *grpc.Server, srv SayServer) { + s.RegisterService(&_Say_serviceDesc, srv) +} + +func _Say_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SayServer).Hello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/greeter.Say/Hello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SayServer).Hello(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +var _Say_serviceDesc = grpc.ServiceDesc{ + ServiceName: "greeter.Say", + HandlerType: (*SayServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Hello", + Handler: _Say_Hello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "hello.proto", +} + +func init() { proto.RegisterFile("hello.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 176 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9, + 0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4f, 0x2f, 0x4a, 0x4d, 0x2d, 0x49, 0x2d, + 0x92, 0x92, 0x49, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x4f, 0x2c, 0xc8, 0xd4, 0x4f, 0xcc, 0xcb, + 0xcb, 0x2f, 0x49, 0x2c, 0xc9, 0xcc, 0xcf, 0x2b, 0x86, 0x28, 0x53, 0x92, 0xe5, 0x62, 0x0f, 0x4a, + 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, + 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0x64, 0xb8, 0x38, 0x82, 0x52, 0x8b, 0x0b, 0xf2, 0xf3, + 0x8a, 0x53, 0x85, 0x04, 0xb8, 0x98, 0x73, 0x8b, 0xd3, 0xa1, 0xd2, 0x20, 0xa6, 0x91, 0x1f, 0x17, + 0x73, 0x70, 0x62, 0xa5, 0x90, 0x3b, 0x17, 0xab, 0x07, 0xc8, 0x66, 0x21, 0x01, 0x3d, 0xa8, 0xa5, + 0x7a, 0x50, 0x33, 0xa5, 0x04, 0x91, 0x44, 0x20, 0xc6, 0x28, 0x49, 0x36, 0x5d, 0x7e, 0x32, 0x99, + 0x49, 0x58, 0x89, 0x4f, 0x1f, 0x2a, 0xa5, 0x0f, 0x76, 0xb6, 0x15, 0xa3, 0x56, 0x12, 0x1b, 0xd8, + 0x4d, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb4, 0x1b, 0x54, 0xae, 0xc9, 0x00, 0x00, 0x00, +} diff --git a/service/grpc/examples/greeter/gateway/proto/hello/hello.pb.gw.go b/service/grpc/examples/greeter/gateway/proto/hello/hello.pb.gw.go new file mode 100644 index 00000000..a49280bb --- /dev/null +++ b/service/grpc/examples/greeter/gateway/proto/hello/hello.pb.gw.go @@ -0,0 +1,112 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: hello.proto + +/* +Package greeter is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package greeter + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray + +func request_Say_Hello_0(ctx context.Context, marshaler runtime.Marshaler, client SayClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Request + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Hello(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterSayHandlerFromEndpoint is same as RegisterSayHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterSayHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterSayHandler(ctx, mux, conn) +} + +// RegisterSayHandler registers the http handlers for service Say to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterSayHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + client := NewSayClient(conn) + + mux.Handle("POST", pattern_Say_Hello_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Say_Hello_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Say_Hello_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Say_Hello_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"greeter", "hello"}, "")) +) + +var ( + forward_Say_Hello_0 = runtime.ForwardResponseMessage +) diff --git a/service/grpc/examples/greeter/gateway/proto/hello/hello.proto b/service/grpc/examples/greeter/gateway/proto/hello/hello.proto new file mode 100644 index 00000000..372bfa42 --- /dev/null +++ b/service/grpc/examples/greeter/gateway/proto/hello/hello.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package greeter; + +import "google/api/annotations.proto"; + +service Say { + rpc Hello(Request) returns (Response) { + option (google.api.http) = { + post: "/greeter/hello" + body: "*" + }; + } +} + +message Request { + string name = 1; +} + +message Response { + string msg = 1; +} diff --git a/service/grpc/examples/greeter/server/main.go b/service/grpc/examples/greeter/server/main.go new file mode 100644 index 00000000..1af0d5c1 --- /dev/null +++ b/service/grpc/examples/greeter/server/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "context" + "log" + + "github.com/micro/go-micro" + "github.com/micro/go-micro/service/grpc" + hello "github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello" +) + +type Say struct{} + +func (s *Say) Hello(ctx context.Context, req *hello.Request, rsp *hello.Response) error { + log.Print("Received Say.Hello request") + rsp.Msg = "Hello " + req.Name + return nil +} + +func main() { + service := grpc.NewService( + micro.Name("go.micro.srv.greeter"), + ) + + // optionally setup command line usage + service.Init() + + // Register Handlers + hello.RegisterSayHandler(service.Server(), new(Say)) + + // Run server + if err := service.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/service/grpc/examples/greeter/server/proto/hello/hello.micro.go b/service/grpc/examples/greeter/server/proto/hello/hello.micro.go new file mode 100644 index 00000000..bf878e25 --- /dev/null +++ b/service/grpc/examples/greeter/server/proto/hello/hello.micro.go @@ -0,0 +1,99 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello/hello.proto + +/* +Package go_micro_srv_greeter is a generated protocol buffer package. + +It is generated from these files: + github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello/hello.proto + +It has these top-level messages: + Request + Response +*/ +package go_micro_srv_greeter + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "context" + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/server" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ client.Option +var _ server.Option + +// Client API for Say service + +type SayService interface { + Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) +} + +type sayService struct { + c client.Client + serviceName string +} + +func NewSayService(serviceName string, c client.Client) SayService { + if c == nil { + c = client.NewClient() + } + if len(serviceName) == 0 { + serviceName = "go.micro.srv.greeter" + } + return &sayService{ + c: c, + serviceName: serviceName, + } +} + +func (c *sayService) Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) { + req := c.c.NewRequest(c.serviceName, "Say.Hello", in) + out := new(Response) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Say service + +type SayHandler interface { + Hello(context.Context, *Request, *Response) error +} + +func RegisterSayHandler(s server.Server, hdlr SayHandler, opts ...server.HandlerOption) { + type say interface { + Hello(ctx context.Context, in *Request, out *Response) error + } + type Say struct { + say + } + h := &sayHandler{hdlr} + s.Handle(s.NewHandler(&Say{h}, opts...)) +} + +type sayHandler struct { + SayHandler +} + +func (h *sayHandler) Hello(ctx context.Context, in *Request, out *Response) error { + return h.SayHandler.Hello(ctx, in, out) +} diff --git a/service/grpc/examples/greeter/server/proto/hello/hello.pb.go b/service/grpc/examples/greeter/server/proto/hello/hello.pb.go new file mode 100644 index 00000000..47288d5d --- /dev/null +++ b/service/grpc/examples/greeter/server/proto/hello/hello.pb.go @@ -0,0 +1,163 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello/hello.proto + +/* +Package go_micro_srv_greeter is a generated protocol buffer package. + +It is generated from these files: + github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello/hello.proto + +It has these top-level messages: + Request + Response +*/ +package go_micro_srv_greeter + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Request struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *Request) Reset() { *m = Request{} } +func (m *Request) String() string { return proto.CompactTextString(m) } +func (*Request) ProtoMessage() {} +func (*Request) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Request) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type Response struct { + Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} +func (*Response) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *Response) GetMsg() string { + if m != nil { + return m.Msg + } + return "" +} + +func init() { + proto.RegisterType((*Request)(nil), "go.micro.srv.greeter.Request") + proto.RegisterType((*Response)(nil), "go.micro.srv.greeter.Response") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Say service + +type SayClient interface { + Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) +} + +type sayClient struct { + cc *grpc.ClientConn +} + +func NewSayClient(cc *grpc.ClientConn) SayClient { + return &sayClient{cc} +} + +func (c *sayClient) Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := grpc.Invoke(ctx, "/go.micro.srv.greeter.Say/Hello", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Say service + +type SayServer interface { + Hello(context.Context, *Request) (*Response, error) +} + +func RegisterSayServer(s *grpc.Server, srv SayServer) { + s.RegisterService(&_Say_serviceDesc, srv) +} + +func _Say_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SayServer).Hello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.srv.greeter.Say/Hello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SayServer).Hello(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +var _Say_serviceDesc = grpc.ServiceDesc{ + ServiceName: "go.micro.srv.greeter.Say", + HandlerType: (*SayServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Hello", + Handler: _Say_Hello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello/hello.proto", +} + +func init() { + proto.RegisterFile("github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello/hello.proto", fileDescriptor0) +} + +var fileDescriptor0 = []byte{ + // 186 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x8e, 0xcd, 0xaa, 0xc2, 0x30, + 0x10, 0x85, 0x6f, 0xe9, 0xfd, 0x33, 0x2b, 0x09, 0x2e, 0x44, 0xac, 0x48, 0x57, 0x6e, 0x4c, 0x40, + 0x5f, 0xa2, 0x3b, 0xa1, 0x3e, 0x41, 0x5b, 0x86, 0xb4, 0xd0, 0x74, 0xe2, 0x4c, 0x5a, 0xf4, 0xed, + 0xa5, 0x31, 0x4b, 0xdd, 0x0c, 0x87, 0xf9, 0x98, 0xf9, 0x8e, 0x28, 0x4c, 0xe7, 0xdb, 0xb1, 0x56, + 0x0d, 0x5a, 0x6d, 0xbb, 0x86, 0x50, 0x1b, 0x3c, 0x1a, 0x72, 0x8d, 0x86, 0x7b, 0x65, 0x5d, 0x0f, + 0xac, 0x0d, 0x01, 0x78, 0x20, 0xcd, 0x40, 0x13, 0x90, 0x76, 0x84, 0x1e, 0x75, 0x0b, 0x7d, 0x1f, + 0xa7, 0x0a, 0x1b, 0xb9, 0x32, 0xa8, 0xc2, 0x07, 0xc5, 0x34, 0xa9, 0x78, 0x94, 0x67, 0xe2, 0xaf, + 0x84, 0xdb, 0x08, 0xec, 0xa5, 0x14, 0xdf, 0x43, 0x65, 0x61, 0x9d, 0xec, 0x93, 0xc3, 0xa2, 0x0c, + 0x39, 0xdf, 0x8a, 0xff, 0x12, 0xd8, 0xe1, 0xc0, 0x20, 0x97, 0x22, 0xb5, 0x6c, 0x22, 0x9e, 0xe3, + 0xe9, 0x22, 0xd2, 0x6b, 0xf5, 0x90, 0x85, 0xf8, 0x29, 0x66, 0x91, 0xcc, 0xd4, 0x3b, 0x87, 0x8a, + 0x82, 0xcd, 0xee, 0x13, 0x7e, 0x09, 0xf2, 0xaf, 0xfa, 0x37, 0x54, 0x3d, 0x3f, 0x03, 0x00, 0x00, + 0xff, 0xff, 0xf2, 0x68, 0x96, 0xe9, 0xf6, 0x00, 0x00, 0x00, +} diff --git a/service/grpc/examples/greeter/server/proto/hello/hello.proto b/service/grpc/examples/greeter/server/proto/hello/hello.proto new file mode 100644 index 00000000..a7c28a44 --- /dev/null +++ b/service/grpc/examples/greeter/server/proto/hello/hello.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package go.micro.srv.greeter; + +service Say { + rpc Hello(Request) returns (Response) {} +} + +message Request { + string name = 1; +} + +message Response { + string msg = 1; +} diff --git a/service/grpc/grpc.go b/service/grpc/grpc.go new file mode 100644 index 00000000..de0f5bfa --- /dev/null +++ b/service/grpc/grpc.go @@ -0,0 +1,58 @@ +package grpc + +import ( + "time" + + "github.com/micro/go-micro" + broker "github.com/micro/go-micro/broker" + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/server" +) + +// NewService returns a grpc service compatible with go-micro.Service +func NewService(opts ...micro.Option) micro.Service { + // our grpc client + c := client.NewClient() + // our grpc server + s := server.NewServer() + // our grpc broker + b := broker.NewBroker() + + // create options with priority for our opts + options := []micro.Option{ + micro.Client(c), + micro.Server(s), + micro.Broker(b), + } + + // append passed in opts + options = append(options, opts...) + + // generate and return a service + return micro.NewService(options...) +} + +// NewFunction returns a grpc service compatible with go-micro.Function +func NewFunction(opts ...micro.Option) micro.Function { + // our grpc client + c := client.NewClient() + // our grpc server + s := server.NewServer() + // our grpc broker + b := broker.NewBroker() + + // create options with priority for our opts + options := []micro.Option{ + micro.Client(c), + micro.Server(s), + micro.Broker(b), + micro.RegisterTTL(time.Minute), + micro.RegisterInterval(time.Second * 30), + } + + // append passed in opts + options = append(options, opts...) + + // generate and return a function + return micro.NewFunction(options...) +} diff --git a/service/grpc/grpc_test.go b/service/grpc/grpc_test.go new file mode 100644 index 00000000..90e6262f --- /dev/null +++ b/service/grpc/grpc_test.go @@ -0,0 +1,178 @@ +package grpc + +import ( + "context" + "crypto/tls" + "sync" + "testing" + + "github.com/micro/go-micro" + "github.com/micro/go-micro/registry/memory" + hello "github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello" + mls "github.com/micro/go-micro/util/tls" +) + +type testHandler struct{} + +func (t *testHandler) Hello(ctx context.Context, req *hello.Request, rsp *hello.Response) error { + rsp.Msg = "Hello " + req.Name + return nil +} + +func TestGRPCService(t *testing.T) { + var wg sync.WaitGroup + wg.Add(1) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create memory registry + r := memory.NewRegistry() + + // create GRPC service + service := NewService( + micro.Name("test.service"), + micro.Registry(r), + micro.AfterStart(func() error { + wg.Done() + return nil + }), + micro.Context(ctx), + ) + + // register test handler + hello.RegisterSayHandler(service.Server(), &testHandler{}) + + // run service + go func() { + if err := service.Run(); err != nil { + t.Fatal(err) + } + }() + + // wait for start + wg.Wait() + + // create client + say := hello.NewSayService("test.service", service.Client()) + + // call service + rsp, err := say.Hello(context.Background(), &hello.Request{ + Name: "John", + }) + if err != nil { + t.Fatal(err) + } + + // check message + if rsp.Msg != "Hello John" { + t.Fatalf("unexpected response %s", rsp.Msg) + } +} + +func TestGRPCFunction(t *testing.T) { + var wg sync.WaitGroup + wg.Add(1) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create service + fn := NewFunction( + micro.Name("test.function"), + micro.Registry(memory.NewRegistry()), + micro.AfterStart(func() error { + wg.Done() + return nil + }), + micro.Context(ctx), + ) + + // register test handler + hello.RegisterSayHandler(fn.Server(), &testHandler{}) + + // run service + go fn.Run() + + // wait for start + wg.Wait() + + // create client + say := hello.NewSayService("test.function", fn.Client()) + + // call service + rsp, err := say.Hello(context.Background(), &hello.Request{ + Name: "John", + }) + if err != nil { + t.Fatal(err) + } + + // check message + if rsp.Msg != "Hello John" { + t.Fatalf("unexpected response %s", rsp.Msg) + } +} + +func TestGRPCTLSService(t *testing.T) { + var wg sync.WaitGroup + wg.Add(1) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create memory registry + r := memory.NewRegistry() + + // create cert + cert, err := mls.Certificate("test.service") + if err != nil { + t.Fatal(err) + } + config := &tls.Config{ + Certificates: []tls.Certificate{cert}, + InsecureSkipVerify: true, + } + + // create GRPC service + service := NewService( + micro.Name("test.service"), + micro.Registry(r), + micro.AfterStart(func() error { + wg.Done() + return nil + }), + micro.Context(ctx), + // set TLS config + WithTLS(config), + ) + + // register test handler + hello.RegisterSayHandler(service.Server(), &testHandler{}) + + // run service + go func() { + if err := service.Run(); err != nil { + t.Fatal(err) + } + }() + + // wait for start + wg.Wait() + + // create client + say := hello.NewSayService("test.service", service.Client()) + + // call service + rsp, err := say.Hello(context.Background(), &hello.Request{ + Name: "John", + }) + if err != nil { + t.Fatal(err) + } + + // check message + if rsp.Msg != "Hello John" { + t.Fatalf("unexpected response %s", rsp.Msg) + } +} diff --git a/service/grpc/options.go b/service/grpc/options.go new file mode 100644 index 00000000..3d224a5e --- /dev/null +++ b/service/grpc/options.go @@ -0,0 +1,21 @@ +package grpc + +import ( + "crypto/tls" + + "github.com/micro/go-micro" + gc "github.com/micro/go-plugins/client/grpc" + gs "github.com/micro/go-plugins/server/grpc" +) + +// WithTLS sets the TLS config for the service +func WithTLS(t *tls.Config) micro.Option { + return func(o *micro.Options) { + o.Client.Init( + gc.AuthTLS(t), + ) + o.Server.Init( + gs.AuthTLS(t), + ) + } +}