├── .gitignore ├── template ├── gitignore.tmpl ├── go-mod.tmpl ├── server.tmpl ├── Makefile.tmpl ├── proto.tmpl ├── cmd_root.tmpl ├── Dockerfile.tmpl ├── server_test.tmpl ├── subscribers.tmpl ├── cmd_up.tmpl ├── cmd_main.tmpl └── client.tmpl ├── docs └── logo.png ├── test ├── test.proto └── test.pb.go ├── Makefile ├── lile ├── main.go └── cmd │ ├── root.go │ ├── new.go │ ├── project.go │ └── file.go ├── registry.go ├── protoc-gen-lile-server ├── template │ ├── unary_unary.tmpl │ ├── unary_unary_test.tmpl │ ├── unary_stream.tmpl │ ├── stream_unary_test.tmpl │ ├── unary_stream_test.tmpl │ ├── stream_unary.tmpl │ ├── stream_stream_test.tmpl │ └── stream_stream.tmpl ├── main_test.go ├── main.go └── statik │ └── statik.go ├── lile_unix.go ├── lile_windows.go ├── .github └── workflows │ ├── test.yml │ └── release.yml ├── .goreleaser.yml ├── cmd.go ├── LICENSE ├── go.mod ├── utils.go ├── run.go ├── lile.go ├── readme.md ├── statik └── statik.go ├── README_zh-CN.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /dist 3 | -------------------------------------------------------------------------------- /template/gitignore.tmpl: -------------------------------------------------------------------------------- 1 | build/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /template/go-mod.tmpl: -------------------------------------------------------------------------------- 1 | module {{ .ModuleName }} 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lileio/lile/HEAD/docs/logo.png -------------------------------------------------------------------------------- /test/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | // The request message containing the user's name. 6 | message Account { 7 | string name = 1; 8 | } 9 | -------------------------------------------------------------------------------- /template/server.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "{{ .ModuleName }}" 5 | ) 6 | 7 | 8 | type {{ .CamelCaseName }}Server struct { 9 | {{ .Name }}.{{ .CamelCaseName }}Server 10 | } 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test statik 2 | test: statik 3 | go test ./... -v -count 1 -p 1 -cover 4 | 5 | statik: 6 | go get github.com/rakyll/statik 7 | statik -src=template 8 | cd protoc-gen-lile-server && statik -src=template 9 | 10 | default: test 11 | -------------------------------------------------------------------------------- /lile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/lileio/lile/v2/lile/cmd" 8 | ) 9 | 10 | func main() { 11 | if err := cmd.RootCmd.Execute(); err != nil { 12 | fmt.Println(err) 13 | os.Exit(-1) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /template/Makefile.tmpl: -------------------------------------------------------------------------------- 1 | # vi: ft=make 2 | .PHONY: proto test 3 | 4 | proto: 5 | go get github.com/golang/protobuf/protoc-gen-go 6 | protoc -I . {{.Name}}.proto --lile-server_out=. --go_out=plugins=grpc,paths=source_relative:. 7 | 8 | test: proto 9 | go test -p 1 -v ./... 10 | -------------------------------------------------------------------------------- /template/proto.tmpl: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | option go_package = "{{ .ModuleName }}"; 3 | package {{ .Name }}; 4 | 5 | message GetRequest { 6 | string id = 1; 7 | } 8 | 9 | message GetResponse { 10 | string id = 1; 11 | } 12 | 13 | service {{ .CamelCaseName }} { 14 | rpc Get(GetRequest) returns (GetResponse) {} 15 | } 16 | -------------------------------------------------------------------------------- /registry.go: -------------------------------------------------------------------------------- 1 | package lile 2 | 3 | // Registry is the interface to implement for external registry providers 4 | type Registry interface { 5 | // Register a service 6 | Register(s *Service) error 7 | // Deregister a service 8 | DeRegister(s *Service) error 9 | // Get a service by name 10 | Get(name string) (string, error) 11 | } 12 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/template/unary_unary.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "errors" 5 | 6 | "context" 7 | {{ dedupImports .InputImport .OutputImport }} 8 | ) 9 | 10 | func (s {{ .ServiceName }}Server) {{ .Name }}(ctx context.Context, r *{{ .InType }}) (*{{ .OutType }}, error) { 11 | return nil, errors.New("not yet implemented") 12 | } 13 | -------------------------------------------------------------------------------- /template/cmd_root.tmpl: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/lileio/lile/v2" 8 | ) 9 | 10 | var cfgFile string 11 | 12 | var RootCmd = lile.BaseCommand("{{ .Name }}", "A gRPC based service") 13 | 14 | func Execute() { 15 | if err := RootCmd.Execute(); err != nil { 16 | fmt.Println(err) 17 | os.Exit(-1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lile/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // RootCmd is the root command line 11 | var RootCmd = &cobra.Command{ 12 | Use: "lile", 13 | } 14 | 15 | // Execute the commands 16 | func Execute() { 17 | if err := RootCmd.Execute(); err != nil { 18 | fmt.Println(err) 19 | os.Exit(-1) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/template/unary_unary_test.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "testing" 5 | 6 | "context" 7 | {{ dedupImports "github.com/stretchr/testify/assert" .InputImport }} 8 | ) 9 | 10 | func Test{{ .Name }}(t *testing.T) { 11 | ctx := context.Background() 12 | req := &{{ .InType }}{} 13 | 14 | res, err := cli.{{ .Name }}(ctx, req) 15 | assert.Nil(t, err) 16 | assert.NotNil(t, res) 17 | } 18 | -------------------------------------------------------------------------------- /lile_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package lile 4 | 5 | import ( 6 | "net" 7 | ) 8 | 9 | func formatPlatformTestSeverAddress(uniquePortion string)(string) { 10 | return "/tmp/" + uniquePortion 11 | } 12 | 13 | func getTestServerListener(address string)(net.Listener, error) { 14 | return net.Listen("unix", address) 15 | } 16 | 17 | func dialTestServer(address string)(net.Conn, error) { 18 | return net.Dial("unix", address) 19 | } 20 | -------------------------------------------------------------------------------- /lile_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package lile 4 | 5 | import ( 6 | "github.com/natefinch/npipe" 7 | "net" 8 | ) 9 | 10 | func formatPlatformTestSeverAddress(uniquePortion string)(string) { 11 | return `\\.\pipe\` + uniquePortion 12 | } 13 | 14 | func getTestServerListener(address string)(net.Listener, error) { 15 | return npipe.Listen(address) 16 | } 17 | 18 | func dialTestServer(address string)(net.Conn, error) { 19 | return npipe.Dial(address) 20 | } 21 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/template/unary_stream.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "errors" 5 | {{ dedupImports .GoPackage .InputImport .OutputImport }} 6 | ) 7 | 8 | func (s {{ .ServiceName }}Server) {{ .Name }}(r *{{ .InType }}, stream {{.ImportName}}.{{ .ServiceName }}_{{.Name}}Server) error { 9 | res := &{{.OutType}}{} 10 | err := stream.Send(res) 11 | if err != nil { 12 | return err 13 | } 14 | 15 | return errors.New("not yet implemented") 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out code 11 | uses: actions/checkout@v1 12 | 13 | - name: Set up Go 14 | uses: actions/setup-go@v1 15 | with: 16 | go-version: 1.13 17 | 18 | - name: Run Unit tests. 19 | run: | 20 | export PATH=$PATH:$(go env GOPATH)/bin 21 | make test 22 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/template/stream_unary_test.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "testing" 5 | 6 | "context" 7 | {{ dedupImports "github.com/stretchr/testify/assert" .InputImport }} 8 | ) 9 | 10 | func Test{{ .Name }}(t *testing.T) { 11 | ctx := context.Background() 12 | stream, err := cli.{{.Name}}(ctx) 13 | assert.Nil(t, err) 14 | 15 | req := &{{.InType}}{} 16 | stream.Send(req) 17 | res, err := stream.CloseAndRecv() 18 | 19 | assert.Nil(t, err) 20 | assert.NotNil(t, res) 21 | } 22 | -------------------------------------------------------------------------------- /template/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS build-env 2 | WORKDIR /{{ .Name }} 3 | RUN apk update && apk upgrade && \ 4 | apk add --no-cache bash git openssh 5 | COPY go.mod /{{ .Name }}/go.mod 6 | COPY go.sum /{{ .Name }}/go.sum 7 | RUN go mod download 8 | COPY . /{{ .Name }} 9 | RUN CGO_ENABLED=0 GOOS=linux go build -o build/{{.Name}} ./{{.Name}} 10 | 11 | 12 | FROM scratch 13 | COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | COPY --from=build-env /{{ .Name }}/build/{{.Name}} / 15 | ENTRYPOINT ["/{{.Name}}"] 16 | CMD ["up", "--grpc-port=80"] 17 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/template/unary_stream_test.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "testing" 5 | "io" 6 | 7 | "context" 8 | {{ dedupImports "github.com/stretchr/testify/assert" .InputImport }} 9 | ) 10 | 11 | func Test{{ .Name }}(t *testing.T) { 12 | ctx := context.Background() 13 | req := &{{ .InType }}{} 14 | 15 | stream, err := cli.{{.Name}}(ctx, req) 16 | assert.Nil(t, err) 17 | 18 | for { 19 | res, err := stream.Recv() 20 | if err == io.EOF { 21 | break 22 | } 23 | 24 | if err != nil { 25 | assert.Fail(t, err.Error()) 26 | break 27 | } 28 | 29 | assert.Nil(t, err) 30 | assert.NotNil(t, res) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /template/server_test.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "google.golang.org/grpc" 8 | 9 | "github.com/lileio/lile/v2" 10 | "{{ .ModuleName }}" 11 | ) 12 | 13 | var s = {{ .CamelCaseName }}Server{} 14 | var cli {{ .Name }}.{{ .CamelCaseName }}Client 15 | 16 | func TestMain(m *testing.M) { 17 | impl := func(g *grpc.Server) { 18 | {{ .Name }}.Register{{ .CamelCaseName }}Server(g, s) 19 | } 20 | 21 | gs := grpc.NewServer() 22 | impl(gs) 23 | 24 | addr, serve := lile.NewTestServer(gs) 25 | go serve() 26 | 27 | cli = {{ .Name }}.New{{ .CamelCaseName }}Client(lile.TestConn(addr)) 28 | 29 | os.Exit(m.Run()) 30 | } 31 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/template/stream_unary.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | 8 | {{ dedupImports .GoPackage .OutputImport }} 9 | ) 10 | 11 | func (s {{ .ServiceName }}Server) {{ .Name }}(stream {{.ImportName}}.{{ .ServiceName }}_{{.Name}}Server) error { 12 | for { 13 | req, err := stream.Recv() 14 | 15 | // Do something useful with req 16 | if req != nil { 17 | fmt.Printf("req = %+v\n", req) 18 | } 19 | 20 | if err == io.EOF { 21 | return stream.SendAndClose(&{{ .OutType }}{}) 22 | } 23 | 24 | if err != nil { 25 | return err 26 | } 27 | 28 | return errors.New("not yet implemented") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/template/stream_stream_test.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | 7 | "context" 8 | {{ dedupImports "github.com/stretchr/testify/assert" .InputImport }} 9 | ) 10 | 11 | func Test{{ .Name }}(t *testing.T) { 12 | ctx := context.Background() 13 | stream, err := cli.{{.Name}}(ctx) 14 | assert.Nil(t, err) 15 | 16 | req := &{{.InType}}{} 17 | stream.Send(req) 18 | err = stream.CloseSend() 19 | assert.Nil(t, err) 20 | 21 | for { 22 | res, err := stream.Recv() 23 | if err == io.EOF { 24 | break 25 | } 26 | 27 | if err != nil { 28 | assert.Nil(t, err) 29 | break 30 | } 31 | 32 | assert.NotNil(t, res) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /template/subscribers.tmpl: -------------------------------------------------------------------------------- 1 | package subscribers 2 | 3 | import ( 4 | "github.com/lileio/pubsub/v2" 5 | ) 6 | 7 | type {{ .CamelCaseName }}ServiceSubscriber struct {} 8 | 9 | func (s *{{ .CamelCaseName }}ServiceSubscriber) Setup(c *pubsub.Client) { 10 | // https://godoc.org/github.com/lileio/pubsub#Client.On 11 | // c.On(pubsub.HandlerOptions{ 12 | // Topic: "some_topic", 13 | // Name: "service_name", 14 | // Handler: s.SomeMethod, 15 | // Deadline: 10 * time.Second, 16 | // Concurrency: 10, 17 | // AutoAck: true, 18 | // }) 19 | } 20 | 21 | // func (s *{{ .CamelCaseName }}ServiceSubscriber) SomeMethod(ctx context.Context, req *proto.Message, _ *pubsub.Msg) error { 22 | // return nil 23 | // } 24 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/template/stream_stream.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | 8 | {{ dedupImports .GoPackage .OutputImport }} 9 | ) 10 | 11 | func (s {{ .ServiceName }}Server) {{ .Name }}(stream {{.ImportName}}.{{ .ServiceName }}_{{.Name}}Server) error { 12 | for { 13 | req, err := stream.Recv() 14 | if err == io.EOF { 15 | break 16 | } 17 | 18 | if err != nil { 19 | return err 20 | } 21 | 22 | // Do something useful with req 23 | if req != nil { 24 | fmt.Printf("req = %+v\n", req) 25 | } 26 | 27 | err = stream.Send(&{{.OutType}}{}) 28 | if err != nil { 29 | return err 30 | } 31 | } 32 | 33 | return errors.New("not yet implemented") 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | name: Release 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check out code 12 | uses: actions/checkout@master 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v1 16 | with: 17 | go-version: 1.13 18 | 19 | - name: Run Unit tests. 20 | run: | 21 | export PATH=$PATH:$(go env GOPATH)/bin 22 | make test 23 | 24 | - name: Run GoReleaser 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }} 27 | uses: goreleaser/goreleaser-action@master 28 | with: 29 | args: release --skip-validate 30 | if: success() 31 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | builds: 5 | - main: ./lile/main.go 6 | id: lile 7 | binary: lile 8 | env: 9 | - CGO_ENABLED=0 10 | - main: ./protoc-gen-lile-server/main.go 11 | id: protoc-gen 12 | binary: protoc-gen-lile-server 13 | env: 14 | - CGO_ENABLED=0 15 | brews: 16 | - name: lile 17 | github: 18 | owner: lileio 19 | name: homebrew-lile 20 | archives: 21 | - replacements: 22 | darwin: Darwin 23 | linux: Linux 24 | windows: Windows 25 | 386: i386 26 | amd64: x86_64 27 | checksum: 28 | name_template: 'checksums.txt' 29 | snapshot: 30 | name_template: "{{ .Tag }}-next" 31 | changelog: 32 | sort: asc 33 | filters: 34 | exclude: 35 | - '^docs:' 36 | - '^test:' 37 | -------------------------------------------------------------------------------- /template/cmd_up.tmpl: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | 8 | "github.com/lileio/pubsub/v2" 9 | "github.com/lileio/lile/v2" 10 | "github.com/spf13/cobra" 11 | "{{ .ModuleName }}/subscribers" 12 | ) 13 | 14 | var upCmd = &cobra.Command{ 15 | Use: "up", 16 | Short: "up runs both RPC service", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | c := make(chan os.Signal, 1) 19 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 20 | 21 | go func() { 22 | lile.Run() 23 | }() 24 | go func() { 25 | pubsub.Subscribe(&subscribers.{{ .CamelCaseName }}ServiceSubscriber{}) 26 | }() 27 | 28 | <-c 29 | lile.Shutdown() 30 | pubsub.Shutdown() 31 | }, 32 | } 33 | 34 | func init() { 35 | RootCmd.AddCommand(upCmd) 36 | } 37 | -------------------------------------------------------------------------------- /template/cmd_main.tmpl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "net/http/pprof" 5 | 6 | "github.com/lileio/lile/v2" 7 | "github.com/lileio/logr" 8 | "github.com/lileio/fromenv" 9 | "github.com/lileio/pubsub/v2" 10 | "github.com/lileio/pubsub/v2/middleware/defaults" 11 | "google.golang.org/grpc" 12 | "{{ .ModuleName }}" 13 | "{{ .ModuleName }}/server" 14 | "{{ .ModuleName }}/{{ .Name }}/cmd" 15 | ) 16 | 17 | func main() { 18 | logr.SetLevelFromEnv() 19 | s := &server.{{ .CamelCaseName }}Server{} 20 | 21 | lile.Name("{{ .Name }}") 22 | lile.Server(func(g *grpc.Server) { 23 | {{ .Name }}.Register{{ .CamelCaseName }}Server(g, s) 24 | }) 25 | 26 | pubsub.SetClient(&pubsub.Client{ 27 | ServiceName: lile.GlobalService().Name, 28 | Provider: fromenv.PubSubProvider(), 29 | Middleware: defaults.Middleware, 30 | }) 31 | 32 | cmd.Execute() 33 | } 34 | -------------------------------------------------------------------------------- /cmd.go: -------------------------------------------------------------------------------- 1 | package lile 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | // BaseCommand provides the basic flags vars for running a service 6 | func BaseCommand(serviceName, shortDescription string) *cobra.Command { 7 | command := &cobra.Command{ 8 | Use: serviceName, 9 | Short: shortDescription, 10 | } 11 | 12 | command.PersistentFlags().StringVar( 13 | &service.Config.Host, 14 | "grpc-host", 15 | "0.0.0.0", 16 | "gRPC service hostname", 17 | ) 18 | 19 | command.PersistentFlags().IntVar( 20 | &service.Config.Port, 21 | "grpc-port", 22 | 8000, 23 | "gRPC port", 24 | ) 25 | 26 | command.PersistentFlags().StringVar( 27 | &service.PrometheusConfig.Host, 28 | "prometheus-host", 29 | "0.0.0.0", 30 | "Prometheus metrics hostname", 31 | ) 32 | 33 | command.PersistentFlags().IntVar( 34 | &service.PrometheusConfig.Port, 35 | "prometheus-port", 36 | 9000, 37 | "Prometheus metrics port", 38 | ) 39 | 40 | return command 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Lileio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /template/client.tmpl: -------------------------------------------------------------------------------- 1 | package {{ .Name }} 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" 7 | "github.com/lileio/lile/v2" 8 | opentracing "github.com/opentracing/opentracing-go" 9 | grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | var ( 14 | cm = &sync.Mutex{} 15 | Client {{ .CamelCaseName }}Client 16 | ) 17 | 18 | func Get{{ .CamelCaseName }}Client() {{ .CamelCaseName }}Client { 19 | cm.Lock() 20 | defer cm.Unlock() 21 | 22 | if Client != nil { 23 | return Client 24 | } 25 | 26 | serviceURL := lile.URLForService("{{ .DNSName }}") 27 | 28 | // We don't need to error here, as this creates a pool and connections 29 | // will happen later 30 | conn, _ := grpc.Dial( 31 | serviceURL, 32 | grpc.WithInsecure(), 33 | grpc.WithUnaryInterceptor( 34 | grpc_middleware.ChainUnaryClient( 35 | lile.ContextClientInterceptor(), 36 | otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer()), 37 | ), 38 | )) 39 | 40 | cli := New{{ .CamelCaseName }}Client(conn) 41 | Client = cli 42 | return cli 43 | } 44 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lileio/lile/v2 2 | 3 | require ( 4 | github.com/beorn7/perks v1.0.0 // indirect 5 | github.com/fatih/color v1.7.0 6 | github.com/gofrs/uuid v3.1.0+incompatible 7 | github.com/golang/protobuf v1.3.2 8 | github.com/googleapis/gax-go v2.0.2+incompatible // indirect 9 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 10 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 11 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 12 | github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7 13 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 14 | github.com/lileio/fromenv v1.4.0 15 | github.com/mattn/go-colorable v0.0.9 16 | github.com/mattn/go-isatty v0.0.7 // indirect 17 | github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce 18 | github.com/onsi/ginkgo v1.10.1 // indirect 19 | github.com/onsi/gomega v1.7.0 // indirect 20 | github.com/prometheus/client_golang v0.9.2 21 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect 22 | github.com/prometheus/common v0.4.0 // indirect 23 | github.com/rakyll/statik v0.1.7-0.20190731211841-925a23bda946 24 | github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 25 | github.com/sirupsen/logrus v1.4.2 26 | github.com/spf13/cobra v0.0.3 27 | github.com/spf13/pflag v1.0.5 // indirect 28 | github.com/stretchr/testify v1.4.0 29 | github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6 30 | github.com/xtgo/set v1.0.0 31 | google.golang.org/api v0.11.0 // indirect 32 | google.golang.org/grpc v1.24.0 33 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect 34 | ) 35 | 36 | go 1.13 37 | -------------------------------------------------------------------------------- /lile/cmd/new.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/fatih/color" 10 | _ "github.com/lileio/lile/v2/statik" 11 | "github.com/mattn/go-colorable" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var ( 16 | dir string 17 | name string 18 | 19 | out = colorable.NewColorableStdout() 20 | newCmd = &cobra.Command{ 21 | Use: "new [name]", 22 | Short: "Create a new service", 23 | Run: new, 24 | } 25 | ) 26 | 27 | func init() { 28 | RootCmd.AddCommand(newCmd) 29 | 30 | newCmd.Flags().StringVar( 31 | &dir, 32 | "dir", 33 | "", 34 | "the directory to create the service", 35 | ) 36 | 37 | newCmd.Flags().StringVar( 38 | &name, 39 | "name", 40 | "", 41 | "the module name i.e (github.com/username/project)", 42 | ) 43 | 44 | newCmd.MarkFlagRequired("name") 45 | } 46 | 47 | func new(cmd *cobra.Command, args []string) { 48 | if dir == "" { 49 | dir = lastFromSplit(name, string(os.PathSeparator)) 50 | } 51 | 52 | fmt.Printf("Creating project in %s\n", dir) 53 | 54 | if !askIsOK() { 55 | fmt.Println("Exiting..") 56 | return 57 | } 58 | 59 | p := newProject(dir, name) 60 | 61 | err := p.write() 62 | if err != nil { 63 | er(err) 64 | } 65 | 66 | p.Folder.print() 67 | } 68 | 69 | func askIsOK() bool { 70 | if os.Getenv("CI") != "" { 71 | return true 72 | } 73 | 74 | fmt.Fprintf(out, "Is this OK? %ses/%so\n", 75 | color.YellowString("[y]"), 76 | color.CyanString("[n]"), 77 | ) 78 | scan := bufio.NewScanner(os.Stdin) 79 | scan.Scan() 80 | return strings.Contains(strings.ToLower(scan.Text()), "y") 81 | } 82 | 83 | func er(err error) { 84 | if err != nil { 85 | fmt.Fprintf(out, "%s: %s \n", 86 | color.RedString("[ERROR]"), 87 | err.Error(), 88 | ) 89 | panic(err) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package lile 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/gofrs/uuid" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | // NewTestServer is a helper function to create a gRPC server on a non-network 12 | // socket and it returns the socket location and a func to call which starts 13 | // the server 14 | func NewTestServer(s *grpc.Server) (string, func()) { 15 | socketAddress, listener, err := getTestServerTransport() 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | return socketAddress, func() { 21 | s.Serve(listener) 22 | } 23 | } 24 | 25 | // TestConn is a connection that connects to a socket based connection 26 | func TestConn(addr string) *grpc.ClientConn { 27 | conn, err := grpc.Dial( 28 | addr, 29 | grpc.WithDialer(func(addr string, d time.Duration) (net.Conn, error) { 30 | return dialTestServer(addr) 31 | }), 32 | grpc.WithInsecure(), 33 | grpc.WithTimeout(1*time.Second), 34 | grpc.WithBlock(), 35 | ) 36 | 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | return conn 42 | } 43 | 44 | // Creates a server listener dependent on the underlying platform. Windows 45 | // hosts will have a Windows Named pipe, anything else gets a UNIX socket 46 | func getTestServerTransport() (string, net.Listener, error) { 47 | var uniqueAddress string 48 | 49 | // Create a random string for part of the address 50 | uid, err := uuid.NewV1() 51 | if err != nil { 52 | return "", nil, err 53 | } 54 | 55 | uniqueAddress = formatPlatformTestSeverAddress(uid.String()) 56 | 57 | serverListener, err := getTestServerListener(uniqueAddress) 58 | if err != nil { 59 | return "", nil, err 60 | } 61 | 62 | return uniqueAddress, serverListener, nil 63 | } 64 | 65 | func generateID(n string) string { 66 | uid, _ := uuid.NewV4() 67 | return n + "-" + uid.String() 68 | } 69 | -------------------------------------------------------------------------------- /lile/cmd/project.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/iancoleman/strcase" 8 | ) 9 | 10 | type project struct { 11 | ModuleName string 12 | Name string 13 | ProjectDir string 14 | Folder folder 15 | } 16 | 17 | func newProject(path, moduleName string) project { 18 | f := folder{ 19 | AbsPath: path, 20 | } 21 | 22 | name := lastFromSplit(name, string(os.PathSeparator)) 23 | 24 | s := f.addFolder("server") 25 | s.addFile("server.go", "server.tmpl") 26 | s.addFile("server_test.go", "server_test.tmpl") 27 | 28 | subs := f.addFolder("subscribers") 29 | subs.addFile("subscribers.go", "subscribers.tmpl") 30 | 31 | cmd := f.addFolder(name) 32 | cmd.addFile("main.go", "cmd_main.tmpl") 33 | 34 | cmds := cmd.addFolder("cmd") 35 | cmds.addFile("root.go", "cmd_root.tmpl") 36 | cmds.addFile("up.go", "cmd_up.tmpl") 37 | 38 | f.addFile(name+".proto", "proto.tmpl") 39 | f.addFile("client.go", "client.tmpl") 40 | f.addFile("Makefile", "Makefile.tmpl") 41 | f.addFile("Dockerfile", "Dockerfile.tmpl") 42 | f.addFile("go.mod", "go-mod.tmpl") 43 | f.addFile(".gitignore", "gitignore.tmpl") 44 | 45 | return project{ 46 | ModuleName: moduleName, 47 | Name: name, 48 | ProjectDir: path, 49 | Folder: f, 50 | } 51 | } 52 | 53 | func (p project) write() error { 54 | err := os.MkdirAll(p.ProjectDir, os.ModePerm) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | return p.Folder.render(p) 60 | } 61 | 62 | // CamelCaseName returns a CamelCased name of the service 63 | func (p project) CamelCaseName() string { 64 | return strcase.ToCamel(p.Name) 65 | } 66 | 67 | // DNSName returns a snake-cased-type name that be used as a URL or packageName 68 | func (p project) DNSName() string { 69 | return strings.Replace(strcase.ToSnake(p.Name), "_", "-", -1) 70 | } 71 | 72 | func lastFromSplit(input, split string) string { 73 | rel := strings.Split(input, split) 74 | return rel[len(rel)-1] 75 | } 76 | -------------------------------------------------------------------------------- /test/test.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: test.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package test is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | test.proto 10 | 11 | It has these top-level messages: 12 | Account 13 | */ 14 | package test 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | // Reference imports to suppress errors if they are not otherwise used. 21 | var _ = proto.Marshal 22 | var _ = fmt.Errorf 23 | var _ = math.Inf 24 | 25 | // This is a compile-time assertion to ensure that this generated file 26 | // is compatible with the proto package it is being compiled against. 27 | // A compilation error at this line likely means your copy of the 28 | // proto package needs to be updated. 29 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 30 | 31 | // The request message containing the user's name. 32 | type Account struct { 33 | Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` 34 | } 35 | 36 | func (m *Account) Reset() { *m = Account{} } 37 | func (m *Account) String() string { return proto.CompactTextString(m) } 38 | func (*Account) ProtoMessage() {} 39 | func (*Account) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 40 | 41 | func (m *Account) GetName() string { 42 | if m != nil { 43 | return m.Name 44 | } 45 | return "" 46 | } 47 | 48 | func init() { 49 | proto.RegisterType((*Account)(nil), "test.Account") 50 | } 51 | 52 | func init() { proto.RegisterFile("test.proto", fileDescriptor0) } 53 | 54 | var fileDescriptor0 = []byte{ 55 | // 73 bytes of a gzipped FileDescriptorProto 56 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e, 57 | 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0x64, 0xb9, 0xd8, 0x1d, 0x93, 58 | 0x93, 0xf3, 0x4b, 0xf3, 0x4a, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15, 59 | 0x18, 0x35, 0x38, 0x83, 0xc0, 0xec, 0x24, 0x36, 0xb0, 0x5a, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 60 | 0xff, 0x79, 0x51, 0x45, 0x8c, 0x39, 0x00, 0x00, 0x00, 61 | } 62 | -------------------------------------------------------------------------------- /lile/cmd/file.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/format" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "text/template" 12 | 13 | "github.com/rakyll/statik/fs" 14 | "github.com/xlab/treeprint" 15 | 16 | _ "github.com/lileio/lile/v2/statik" 17 | ) 18 | 19 | type file struct { 20 | Name string 21 | AbsPath string 22 | Template string 23 | } 24 | 25 | type folder struct { 26 | AbsPath string 27 | 28 | // Unexported so you can't set them without methods 29 | files []file 30 | folders []*folder 31 | } 32 | 33 | func (f *folder) addFolder(name string) *folder { 34 | newF := &folder{ 35 | AbsPath: filepath.Join(f.AbsPath, name), 36 | } 37 | f.folders = append(f.folders, newF) 38 | return newF 39 | } 40 | 41 | func (f *folder) addFile(name, tmpl string) { 42 | f.files = append(f.files, file{ 43 | Name: name, 44 | Template: tmpl, 45 | AbsPath: filepath.Join(f.AbsPath, name), 46 | }) 47 | } 48 | 49 | func (f *folder) render(p project) error { 50 | hfs, err := fs.New() 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | for _, v := range f.files { 56 | b, err := fs.ReadFile(hfs, "/"+v.Template) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | t, err := template.New(v.Template).Parse(string(b)) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | file, err := os.Create(v.AbsPath) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | defer file.Close() 72 | 73 | if strings.Contains(v.AbsPath, ".go") { 74 | var out bytes.Buffer 75 | err = t.Execute(&out, p) 76 | if err != nil { 77 | log.Printf("Could not process template %s\n", v) 78 | return err 79 | } 80 | 81 | b, err := format.Source(out.Bytes()) 82 | if err != nil { 83 | fmt.Print(string(out.Bytes())) 84 | log.Printf("\nCould not format Go file %s\n", v) 85 | return err 86 | } 87 | 88 | _, err = file.Write(b) 89 | if err != nil { 90 | return err 91 | } 92 | } else { 93 | err = t.Execute(file, p) 94 | if err != nil { 95 | return err 96 | } 97 | } 98 | } 99 | 100 | for _, v := range f.folders { 101 | err := os.Mkdir(v.AbsPath, os.ModePerm) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | err = v.render(p) 107 | if err != nil { 108 | return err 109 | } 110 | } 111 | 112 | return nil 113 | } 114 | 115 | func (f folder) print() { 116 | t := f.tree(true, treeprint.New()) 117 | fmt.Println(t.String()) 118 | } 119 | 120 | func (f folder) tree(root bool, tree treeprint.Tree) treeprint.Tree { 121 | if !root { 122 | tree = tree.AddBranch(lastFromSplit(f.AbsPath, string(os.PathSeparator))) 123 | } 124 | 125 | for _, v := range f.folders { 126 | v.tree(false, tree) 127 | } 128 | 129 | for _, v := range f.files { 130 | tree.AddNode(v.Name) 131 | } 132 | 133 | return tree 134 | } 135 | -------------------------------------------------------------------------------- /run.go: -------------------------------------------------------------------------------- 1 | package lile 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 10 | grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 11 | "github.com/prometheus/client_golang/prometheus" 12 | "github.com/prometheus/client_golang/prometheus/promhttp" 13 | "github.com/sirupsen/logrus" 14 | "google.golang.org/grpc" 15 | ) 16 | 17 | // Run is a blocking cmd to run the gRPC and metrics server. 18 | // You should listen to os signals and call Shutdown() if you 19 | // want a graceful shutdown or want to handle other goroutines 20 | func Run() error { 21 | if service.Registry != nil { 22 | service.Registry.Register(service) 23 | } 24 | 25 | // Start a metrics server in the background 26 | startPrometheusServer() 27 | 28 | // Create and then server a gRPC server 29 | err := ServeGRPC() 30 | if service.Registry != nil { 31 | service.Registry.DeRegister(service) 32 | } 33 | return err 34 | } 35 | 36 | // ServeGRPC creates and runs a blocking gRPC server 37 | func ServeGRPC() error { 38 | var err error 39 | service.ServiceListener, err = net.Listen("tcp", service.Config.Address()) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | logrus.Infof("Serving gRPC on %s", service.Config.Address()) 45 | return createGrpcServer().Serve(service.ServiceListener) 46 | } 47 | 48 | // Shutdown gracefully shuts down the gRPC and metrics servers 49 | func Shutdown() { 50 | logrus.Infof("lile: Gracefully shutting down gRPC and Prometheus") 51 | 52 | if service.Registry != nil { 53 | service.Registry.DeRegister(service) 54 | } 55 | 56 | service.GRPCServer.GracefulStop() 57 | 58 | // 30 seconds is the default grace period in Kubernetes 59 | ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) 60 | defer cancel() 61 | if err := service.PrometheusServer.Shutdown(ctx); err != nil { 62 | logrus.Infof("Timeout during shutdown of metrics server. Error: %v", err) 63 | } 64 | } 65 | 66 | func createGrpcServer() *grpc.Server { 67 | service.GRPCOptions = append(service.GRPCOptions, grpc.UnaryInterceptor( 68 | grpc_middleware.ChainUnaryServer(service.UnaryInts...))) 69 | 70 | service.GRPCOptions = append(service.GRPCOptions, grpc.StreamInterceptor( 71 | grpc_middleware.ChainStreamServer(service.StreamInts...))) 72 | 73 | service.GRPCServer = grpc.NewServer( 74 | service.GRPCOptions..., 75 | ) 76 | 77 | service.GRPCImplementation(service.GRPCServer) 78 | 79 | grpc_prometheus.EnableHandlingTimeHistogram( 80 | func(opt *prometheus.HistogramOpts) { 81 | opt.Buckets = prometheus.ExponentialBuckets(0.005, 1.4, 20) 82 | }, 83 | ) 84 | 85 | grpc_prometheus.Register(service.GRPCServer) 86 | return service.GRPCServer 87 | } 88 | 89 | func startPrometheusServer() { 90 | service.PrometheusServer = &http.Server{Addr: service.PrometheusConfig.Address()} 91 | 92 | http.Handle("/metrics", promhttp.Handler()) 93 | logrus.Infof("Prometheus metrics at http://%s/metrics", service.PrometheusConfig.Address()) 94 | 95 | go func() { 96 | if err := service.PrometheusServer.ListenAndServe(); err != nil { 97 | // cannot panic, because this probably is an intentional close 98 | logrus.Errorf("Prometheus http server: ListenAndServe() error: %s", err) 99 | } 100 | }() 101 | } 102 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "testing" 7 | 8 | "github.com/golang/protobuf/proto" 9 | protodescriptor "github.com/golang/protobuf/protoc-gen-go/descriptor" 10 | plugin "github.com/golang/protobuf/protoc-gen-go/plugin" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestGenerator(t *testing.T) { 15 | req := &plugin.CodeGeneratorRequest{ 16 | FileToGenerate: []string{"example.proto"}, 17 | ProtoFile: []*protodescriptor.FileDescriptorProto{ 18 | { 19 | Name: proto.String("empty.proto"), 20 | Package: proto.String("google.protobuf"), 21 | Options: &protodescriptor.FileOptions{ 22 | GoPackage: proto.String("github.com/golang/protobuf/ptypes/empty"), 23 | }, 24 | }, 25 | stubFile(), 26 | }, 27 | } 28 | 29 | data, err := proto.Marshal(req) 30 | if err != nil { 31 | log.Fatal("marshaling error: ", err) 32 | } 33 | 34 | r := bytes.NewReader(data) 35 | w := &bytes.Buffer{} 36 | gen(r, w) 37 | 38 | var res plugin.CodeGeneratorResponse 39 | err = proto.Unmarshal(w.Bytes(), &res) 40 | if err != nil { 41 | log.Fatal("unmarshaling error: ", err) 42 | } 43 | 44 | assert.Nil(t, res.Error) 45 | assert.Equal(t, len(res.File), 10) 46 | } 47 | 48 | func stubFile() *protodescriptor.FileDescriptorProto { 49 | msgdesc := &protodescriptor.DescriptorProto{ 50 | Name: proto.String("ExampleMessage"), 51 | } 52 | 53 | unary_meth := &protodescriptor.MethodDescriptorProto{ 54 | Name: proto.String("Example"), 55 | InputType: proto.String("example.ExampleMessage"), 56 | OutputType: proto.String("example.ExampleMessage"), 57 | } 58 | 59 | custom_type_meth := &protodescriptor.MethodDescriptorProto{ 60 | Name: proto.String("ExampleWithoutBindings"), 61 | InputType: proto.String("google.protobuf.Empty"), 62 | OutputType: proto.String("google.protobuf.Empty"), 63 | } 64 | 65 | unary_stream_meth := &protodescriptor.MethodDescriptorProto{ 66 | Name: proto.String("Example"), 67 | InputType: proto.String("example.ExampleMessage"), 68 | OutputType: proto.String("example.ExampleMessage"), 69 | ServerStreaming: proto.Bool(true), 70 | } 71 | 72 | stream_unary_meth := &protodescriptor.MethodDescriptorProto{ 73 | Name: proto.String("Example"), 74 | InputType: proto.String("example.ExampleMessage"), 75 | OutputType: proto.String("example.ExampleMessage"), 76 | ClientStreaming: proto.Bool(true), 77 | } 78 | 79 | stream_stream_meth := &protodescriptor.MethodDescriptorProto{ 80 | Name: proto.String("Example"), 81 | InputType: proto.String("example.ExampleMessage"), 82 | OutputType: proto.String("example.ExampleMessage"), 83 | ClientStreaming: proto.Bool(true), 84 | ServerStreaming: proto.Bool(true), 85 | } 86 | 87 | svc := &protodescriptor.ServiceDescriptorProto{ 88 | Name: proto.String("ExampleService"), 89 | Method: []*protodescriptor.MethodDescriptorProto{ 90 | unary_meth, 91 | custom_type_meth, 92 | unary_stream_meth, 93 | stream_unary_meth, 94 | stream_stream_meth, 95 | }, 96 | } 97 | 98 | gopkg := "github.com/example/example" 99 | return &protodescriptor.FileDescriptorProto{ 100 | Name: proto.String("example.proto"), 101 | Options: &protodescriptor.FileOptions{GoPackage: &gopkg}, 102 | Package: proto.String("example"), 103 | Dependency: []string{"google/protobuf/empty.proto"}, 104 | MessageType: []*protodescriptor.DescriptorProto{msgdesc}, 105 | Service: []*protodescriptor.ServiceDescriptorProto{svc}, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lile.go: -------------------------------------------------------------------------------- 1 | // Package lile provides helper methods to quickly create RPC based services 2 | // that have metrics, tracing and pub/sub support 3 | package lile 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "net" 9 | "net/http" 10 | "strings" 11 | 12 | grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" 13 | grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 14 | "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" 15 | "github.com/lileio/fromenv" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/metadata" 18 | ) 19 | 20 | var ( 21 | service = NewService("lile") 22 | ) 23 | 24 | // RegisterImplementation allows you to register your gRPC server 25 | type RegisterImplementation func(s *grpc.Server) 26 | 27 | // ServerConfig is a generic server configuration 28 | type ServerConfig struct { 29 | Port int 30 | Host string 31 | } 32 | 33 | // Address Gets a logical addr for a ServerConfig 34 | func (c *ServerConfig) Address() string { 35 | return fmt.Sprintf("%s:%d", c.Host, c.Port) 36 | } 37 | 38 | // Service is a gRPC based server with extra features 39 | type Service struct { 40 | ID string 41 | Name string 42 | 43 | // Interceptors 44 | UnaryInts []grpc.UnaryServerInterceptor 45 | StreamInts []grpc.StreamServerInterceptor 46 | 47 | // The RPC server implementation 48 | GRPCImplementation RegisterImplementation 49 | GRPCOptions []grpc.ServerOption 50 | 51 | // gRPC and Prometheus endpoints 52 | Config ServerConfig 53 | PrometheusConfig ServerConfig 54 | 55 | // Registry allows Lile to work with external registeries like 56 | // consul, zookeeper or similar 57 | Registry Registry 58 | 59 | // Private utils, exposed so they can be useful if needed 60 | ServiceListener net.Listener 61 | GRPCServer *grpc.Server 62 | PrometheusServer *http.Server 63 | } 64 | 65 | // NewService creates a new service with a given name 66 | func NewService(n string) *Service { 67 | return &Service{ 68 | ID: generateID(n), 69 | Name: n, 70 | Config: ServerConfig{Host: "0.0.0.0", Port: 8000}, 71 | PrometheusConfig: ServerConfig{Host: "0.0.0.0", Port: 9000}, 72 | GRPCImplementation: func(s *grpc.Server) {}, 73 | UnaryInts: []grpc.UnaryServerInterceptor{ 74 | grpc_prometheus.UnaryServerInterceptor, 75 | grpc_recovery.UnaryServerInterceptor(), 76 | }, 77 | StreamInts: []grpc.StreamServerInterceptor{ 78 | grpc_prometheus.StreamServerInterceptor, 79 | grpc_recovery.StreamServerInterceptor(), 80 | }, 81 | } 82 | } 83 | 84 | // GlobalService returns the global service 85 | func GlobalService() *Service { 86 | return service 87 | } 88 | 89 | // Name sets the name for the service 90 | func Name(n string) { 91 | service.ID = generateID(n) 92 | service.Name = n 93 | AddUnaryInterceptor(otgrpc.OpenTracingServerInterceptor( 94 | fromenv.Tracer(n))) 95 | } 96 | 97 | // Server attaches the gRPC implementation to the service 98 | func Server(r func(s *grpc.Server)) { 99 | service.GRPCImplementation = r 100 | } 101 | 102 | // AddUnaryInterceptor adds a unary interceptor to the RPC server 103 | func AddUnaryInterceptor(unint grpc.UnaryServerInterceptor) { 104 | service.UnaryInts = append(service.UnaryInts, unint) 105 | } 106 | 107 | // AddStreamInterceptor adds a stream interceptor to the RPC server 108 | func AddStreamInterceptor(sint grpc.StreamServerInterceptor) { 109 | service.StreamInts = append(service.StreamInts, sint) 110 | } 111 | 112 | // URLForService returns a service URL via a registry or a simple DNS name 113 | // if not available via the registry 114 | func URLForService(name string) string { 115 | if service.Registry != nil { 116 | url, err := service.Registry.Get(name) 117 | if err != nil { 118 | fmt.Printf("lile: error contacting registry for service %s. err: %s \n", name, err.Error()) 119 | } 120 | return url 121 | } 122 | 123 | return fmt.Sprintf("%s%s", name, ":80") 124 | 125 | } 126 | 127 | // ContextClientInterceptor passes around headers for tracing and linkerd 128 | func ContextClientInterceptor() grpc.UnaryClientInterceptor { 129 | return func( 130 | ctx context.Context, 131 | method string, 132 | req, resp interface{}, 133 | cc *grpc.ClientConn, 134 | invoker grpc.UnaryInvoker, 135 | opts ...grpc.CallOption, 136 | ) error { 137 | pairs := make([]string, 0) 138 | 139 | if md, ok := metadata.FromIncomingContext(ctx); ok { 140 | for key, values := range md { 141 | if strings.HasPrefix(strings.ToLower(key), "x-") { 142 | for _, value := range values { 143 | pairs = append(pairs, key, value) 144 | } 145 | } 146 | } 147 | } 148 | 149 | ctx = metadata.AppendToOutgoingContext(ctx, pairs...) 150 | return invoker(ctx, method, req, resp, cc, opts...) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/format" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "runtime" 13 | "sort" 14 | "strings" 15 | "text/template" 16 | 17 | "github.com/golang/protobuf/protoc-gen-go/descriptor" 18 | "github.com/rakyll/statik/fs" 19 | "github.com/serenize/snaker" 20 | "github.com/xtgo/set" 21 | 22 | "github.com/fatih/color" 23 | "github.com/golang/protobuf/proto" 24 | plugin "github.com/golang/protobuf/protoc-gen-go/plugin" 25 | 26 | _ "github.com/lileio/lile/v2/protoc-gen-lile-server/statik" // TODO: Replace with the absolute import path 27 | ) 28 | 29 | var ( 30 | input io.Reader 31 | output io.Writer 32 | ) 33 | 34 | type grpcMethod struct { 35 | ServiceName string 36 | ImportName string 37 | Name string 38 | InType string 39 | OutType string 40 | ClientStreaming bool 41 | ServerStreaming bool 42 | 43 | GoPackage string 44 | InputImport string 45 | OutputImport string 46 | } 47 | 48 | // Used to import other packages correctly, for example.. 49 | // Package: "google.protobuf" 50 | // GoPackage: "github.com/golang/protobuf/ptypes/empty" 51 | // we replace all occurences of Package with the end of GoPackage and import it 52 | type goimport struct { 53 | Package string 54 | GoPackage string 55 | GoType string 56 | } 57 | 58 | func main() { 59 | gen(os.Stdin, os.Stdout) 60 | } 61 | 62 | func gen(i io.Reader, o io.Writer) { 63 | input = i 64 | output = o 65 | 66 | // Force color output 67 | if runtime.GOOS != "windows" { 68 | color.NoColor = false 69 | } 70 | 71 | // Parse the incoming protobuf request 72 | req, err := parseReq(input) 73 | if err != nil { 74 | emitError(err) 75 | log.Fatal(err) 76 | } 77 | 78 | path := "./server" 79 | if req.Parameter != nil { 80 | path = *req.Parameter 81 | } 82 | 83 | files := []*plugin.CodeGeneratorResponse_File{} 84 | imports := []goimport{} 85 | 86 | for _, file := range req.ProtoFile { 87 | pkgParts := strings.Split(file.GetOptions().GetGoPackage(), "/") 88 | imports = append(imports, goimport{ 89 | Package: file.GetPackage(), 90 | GoPackage: file.GetOptions().GetGoPackage(), 91 | GoType: pkgParts[len(pkgParts)-1], 92 | }) 93 | 94 | // Only generate methods for this file/project 95 | if *file.Name != req.FileToGenerate[0] { 96 | if file.Options == nil || file.Options.GoPackage == nil { 97 | log.Printf("No go_package option defined for import %s, skipping", *file.Name) 98 | } 99 | 100 | continue 101 | } 102 | 103 | if file.Options == nil || file.Options.GoPackage == nil { 104 | log.Printf("No go_package option defined in %s", *file.Name) 105 | 106 | continue 107 | } 108 | 109 | pkgSplit := strings.Split(file.Options.GetGoPackage(), "/") 110 | 111 | for _, service := range file.Service { 112 | for _, method := range service.Method { 113 | gm := grpcMethod{ 114 | ServiceName: service.GetName(), 115 | GoPackage: file.Options.GetGoPackage(), 116 | ImportName: pkgSplit[len(pkgSplit)-1], 117 | Name: method.GetName(), 118 | InType: toGoType(imports, method.GetInputType()), 119 | OutType: toGoType(imports, method.GetOutputType()), 120 | ClientStreaming: method.GetClientStreaming(), 121 | ServerStreaming: method.GetServerStreaming(), 122 | 123 | InputImport: inputImport(imports, method), 124 | OutputImport: outputImport(imports, method), 125 | } 126 | 127 | f, err := generateMethod(path, gm) 128 | if err != nil { 129 | emitError(err) 130 | log.Fatal(err) 131 | } 132 | 133 | if len(f) > 0 { 134 | for _, v := range f { 135 | files = append(files, v) 136 | } 137 | } 138 | } 139 | } 140 | } 141 | 142 | emitFiles(files) 143 | } 144 | 145 | func DedupImports(imports ...string) string { 146 | data := sort.StringSlice(imports) 147 | sort.Sort(data) 148 | n := set.Uniq(data) 149 | imports = data[:n] 150 | 151 | return fmt.Sprintf("\t\"%s\"", strings.Join(imports, "\"\n\t\"")) 152 | } 153 | 154 | func toGoType(imports []goimport, t string) string { 155 | t = strings.Trim(t, ".") 156 | for _, i := range imports { 157 | if strings.Contains(t, i.Package) { 158 | s := strings.Replace(t, i.Package, i.GoType, 1) 159 | s = strings.Replace(s, "-", "_", -1) 160 | return s 161 | } 162 | } 163 | 164 | return t 165 | } 166 | 167 | func inputImport(imports []goimport, method *descriptor.MethodDescriptorProto) string { 168 | for _, i := range imports { 169 | if strings.Contains(method.GetInputType(), i.Package) { 170 | return i.GoPackage 171 | } 172 | } 173 | 174 | return "" 175 | } 176 | 177 | func outputImport(imports []goimport, method *descriptor.MethodDescriptorProto) string { 178 | for _, i := range imports { 179 | if strings.Contains(method.GetOutputType(), i.Package) { 180 | return i.GoPackage 181 | } 182 | } 183 | 184 | return "" 185 | } 186 | 187 | func generateMethod(basePath string, m grpcMethod) ([]*plugin.CodeGeneratorResponse_File, error) { 188 | path := filepath.Join(basePath, snaker.CamelToSnake(m.Name)+".go") 189 | test_path := filepath.Join(basePath, snaker.CamelToSnake(m.Name)+"_test.go") 190 | 191 | template := fmt.Sprintf("%s_%s", 192 | streamFromBool(m.ClientStreaming), 193 | streamFromBool(m.ServerStreaming), 194 | ) 195 | 196 | // If the file exists then just skip the creation 197 | _, err := os.Stat(path) 198 | if err == nil { 199 | log.Printf("%s %s", color.YellowString("[Skipping]"), path) 200 | return nil, nil 201 | } 202 | 203 | files := []*plugin.CodeGeneratorResponse_File{} 204 | 205 | log.Printf("%s %s", color.GreenString("[Creating]"), path) 206 | f, err := render(path, template+".tmpl", m) 207 | if err != nil { 208 | return nil, err 209 | } 210 | 211 | files = append(files, f) 212 | 213 | log.Printf("%s %s", color.CyanString("[Creating test]"), test_path) 214 | f, err = render(test_path, template+"_test.tmpl", m) 215 | 216 | files = append(files, f) 217 | return files, err 218 | } 219 | 220 | func streamFromBool(streaming bool) string { 221 | if streaming { 222 | return "stream" 223 | } 224 | 225 | return "unary" 226 | } 227 | 228 | func render(path, tmpl string, m grpcMethod) (*plugin.CodeGeneratorResponse_File, error) { 229 | hfs, err := fs.New() 230 | if err != nil { 231 | return nil, err 232 | } 233 | 234 | t := template.New(tmpl) 235 | funcMap := template.FuncMap{ 236 | "dedupImports": DedupImports, 237 | } 238 | 239 | t = t.Funcs(funcMap) 240 | b, err := fs.ReadFile(hfs, "/"+tmpl) 241 | if err != nil { 242 | return nil, err 243 | } 244 | 245 | t, err = t.Parse(string(b)) 246 | if err != nil { 247 | return nil, err 248 | } 249 | 250 | var out bytes.Buffer 251 | err = t.Execute(&out, m) 252 | if err != nil { 253 | log.Printf("%s couldn't create template %s, %s", color.RedString("[ERROR]"), tmpl, err) 254 | return nil, err 255 | } 256 | 257 | b, err = format.Source(out.Bytes()) 258 | if err != nil { 259 | log.Printf(string(out.Bytes())) 260 | log.Printf("\n%s couldn't format Go file %s, %s", color.RedString("[ERROR]"), tmpl, err) 261 | return nil, err 262 | } 263 | 264 | str := string(b) 265 | 266 | return &plugin.CodeGeneratorResponse_File{ 267 | Name: &path, 268 | Content: &str, 269 | }, nil 270 | } 271 | 272 | func parseReq(r io.Reader) (*plugin.CodeGeneratorRequest, error) { 273 | input, err := ioutil.ReadAll(r) 274 | if err != nil { 275 | log.Fatalf("Failed to read code generator request: %v", err) 276 | return nil, err 277 | } 278 | 279 | req := new(plugin.CodeGeneratorRequest) 280 | if err = proto.Unmarshal(input, req); err != nil { 281 | log.Fatalf("Failed to unmarshal code generator request: %v", err) 282 | return nil, err 283 | } 284 | 285 | return req, nil 286 | } 287 | 288 | func emitFiles(files []*plugin.CodeGeneratorResponse_File) { 289 | emitResp(&plugin.CodeGeneratorResponse{File: files}) 290 | } 291 | 292 | func emitError(err error) { 293 | emitResp(&plugin.CodeGeneratorResponse{Error: proto.String(err.Error())}) 294 | } 295 | 296 | func emitResp(resp *plugin.CodeGeneratorResponse) { 297 | buf, err := proto.Marshal(resp) 298 | if err != nil { 299 | log.Fatal(err) 300 | } 301 | if _, err := output.Write(buf); err != nil { 302 | log.Fatal(err) 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /protoc-gen-lile-server/statik/statik.go: -------------------------------------------------------------------------------- 1 | // Code generated by statik. DO NOT EDIT. 2 | 3 | // Package statik contains static assets. 4 | package statik 5 | 6 | import ( 7 | "github.com/rakyll/statik/fs" 8 | ) 9 | 10 | func init() { 11 | data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00 \x00stream_stream.tmplUT\x05\x00\x01\xe0a\xed\\\x8c\x90M\x8b\xdb0\x10\x86\xcf\x9a_1\x15\xb4\xd84(\xf7\x82o\xfd\xa0\x97$4=\x16\x8a\xd7\x1e'\"\x91\xe4\x8cd\x87 \xf4\xdf\x17K a\xd9\xcb\x9e\x06\xde\x8fG/\x1a\xdb\xee\xd4\x1e\x08=\xf1L\x0c\xa0\xcd\xe88`\x05B\x12\xb3c/\x01Q\x0e&\xe4\xab\x9d\x04@\x8c\x11{\xea\xa7\xf1w\x0e{T\xbf\xdc\xee\xceQ\xdb)\x8cS(\x0e\xa6\x045\xc00\xd9\x0e+\xbf\xd4\xd4\x9ex\xd6\x1dmZC\x98\xd2>\xbfZg\xe7.U>0\xb5\x06cT\x05\xb2\xe8)\xa9\xf7\xe5\xff1\xaab>0y1F\x10C9\x82\xe9\xb2ZT\xfc\xd6`\xe1\xaa?\xd4\xcdU\x0dB\xe8!;M\x83\xda\xa9\x1f\xdb\x9f\xb9 ^\x98\xda\x13\x08\x91\xe0\x19\xf9\xd4\xa0\xd5\xe7\xe23\x85\x89\xed\"?B\xeb5~w\xe8\x9d\xa1p\xd4\xf6\x80\x93\xa7a:\xe3U\x87#2]\n\x86\xe9\xf2\x063\x98\xa0v\xacm\x18*\xb9x\x0d~\xfe:\xff\xb3r\xb5$\xeb\x82F\xc4\xb2\xf01}O\xb6\xaf\xbe\xc4\xb8\xfc\xf1\xdf\xdbH)\xc5T\x7fhf^\xfa\xd4\x1c{\xb5\xa1k%\xad\x0bx\xa3\x80\xda\x8cg2d\x03\xf5\xb2\x86\x04\xaf\x01\x00\x00\xff\xffPK\x07\x08\x12 \xef\xe40\x01\x00\x00\x16\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00 \x00stream_stream_test.tmplUT\x05\x00\x01\xe0a\xed\\t\x8e\xc1j\xf30\x10\x84\xcf\xda\xa7\xd8_\x87\x1f\xbb\x14\xe5^\xf0\xa5\xa5\x85\\Rh\xf3\x02\x8e\xbcvDl\xc9\x91\xd6!A\xe8\xdd\x8be\xb7\x85\xd2\x9eg\xbe\xf9f\xac\xf5\xa9\xee\x08\x03\xf9\x0by\x003\x8c\xce3\x16\x80(\x8d\x93 $S`c; \xa4v\x96\xe9\xca\x12\x10c\xc4\x86\x9ai\xdcf \xa0\xec\x0c\x1f\xa7\x83\xd2n\xd8\x04\xf6\xc4\xfa\xe87\x99mo\x9b:\x04\xf2,Qm\xed8\xf1\x82`JP\x02\xb4\x93\xd5\xb8\xa7\xc01\xa2\xda\xd5\x03aJ\x05\xe3\xdd\xaaU\xfb\x12#\x08\xcdW|\xa8p\xf5\xab\xc7Z\x9f:\xef&\xdb\x14% \xce\xbez\xb8G\xf2>\xb7z\xa3b\xccc)\x15\x9a\xaf%\x88\xe5\x81\xda\x99\xbe\xe0\\,\x01\x10=\x9dg\xe0\x7f\x8cjk\xf7\xb7\x91R\x8a \xc4\xb2\xa7\xde\xc96\x85\xa7s b^\xaeV\x8fz\xea]\xa0\x1c\xfe1,Z\xe7\xe7\xd7\xc2S\xf8z\xb5\xc2o\xa4/3'L\x9b\x93\xaaB\xe3\xd4\xf3\xebK\x06\xc4\xc1S}\x02!\x12|W\xfeUhM\xbf\xe4\xbf\xe8~R\x9f\x15\xc7k\xcbS(A$H\xf0\x11\x00\x00\xff\xffPK\x07\x08\x89.1q#\x01\x00\x00\xee\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00 \x00stream_unary.tmplUT\x05\x00\x01\xe0a\xed\\d\x8fO\x8b\xdb0\x10\xc5\xcf\x9aO1\x15\xb4\xd84(\xf7\x82\x0f\xa5\xff\xe8% M\x8f\x85b\xecq\"\xd6\x92\x9c\x91\xe4\x10\x84\xbe\xfb\";a\x97]]F\xbc7\xf3\x9b7S\xdb=\xb5'BO<\x13\x03h39\x0eX\x81\x90\xc4\xec\xd8K@\x94\x83 K\xd5N\x02 \xa6\x84=\xf5q\xfa\xbd4{T\xbf\xdc\xe1\xceQ\xfb\x18\xa6\x18V\x07s\x86\x1a`\x88\xb6\xc3\xca\x971u$\x9euG\xbb\xd6\x10\xe6|\\\xb6\xd6\x8bs\x97*\x1f\x98Z\x83)\xa9\x15R\xf4\x9c\xd5\xfb\xe1\xff)\xa9\xd5|`\x96\xc4\x98@\x0ck\x11L\x97MQ\xf1K\x83+W\xfd\xa1n\xaej\x00!\xb6[\xfc\xee\xd0;C\xe1\xac\xed \xa3\xa7!\x8ex\xd5\xe1\x8cL\x17\x10B\x0f\xe5\x83\x1f\x1a\xb4z\\\x80b0A\x1dX\xdb0T\xb2x\x0d~\xfc<\xff\xb3rS:k\x10\"\xc3:X\xb66\x0dj\xa7~\xec\x7fb\x02\\\x1eS\x88l\x1fY\x8ed\xfb\xaf\xb6\xff6:O\xd5\xa7r\xe1>\x86\xbf\xb7\xa9\\\x97\xf2[\xda\xeb\x18w\x0e1?\x9a^\x14\xc7^\xed\xe8ZI\xeb\x02\xde(\xa06\xd3H\x86l\xa0^\xd6 2dx\x0e\x00\x00\xff\xffPK\x07\x08\x0dG\xdd\xef3\x01\x00\x00\xf7\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16\x00 \x00stream_unary_test.tmplUT\x05\x00\x01\xe0a\xed\\l\x8e\xb1n\xeb0\x0cEg\xf1+\x08\x0d\x0f\xd2C\xa1\xec\x05:\xb4\x9d\xb2dh\xf3\x03\xaa\xcc8Fl\xc9\xa1\xe8\xc0\x81\xa0\x7f/b\x1b\x99\xba^\xde{\x0eG\x1f.\xbe%\xcc\xc47b\x80n\x18\x13\x0b\x1aPZ(K\x17[\x0d\xa0tHQh\x16\x0d\x88\xa5`C\xcd4\xee\x97fF\xddvr\x9e~\\H\xc3.\x0b\x93\x843\xef\x96\xed\xe9\xbe\xf39\x13\x8bF\xb7\x8f\xe3$\xeb\x04k\x05\x0bp\x9ab\xc0#e)\x05\xdd\xc1\x0f\x84\xb5\x1a\xc1\xff\x9b\xd6\x1d-\x16PAf|}\xc3\xcd\xef>|\xb8\xb4\x9c\xa6\xd8\x18\x0b\x88\x0f\x9f\x1f^\x90\x98\x97V\xdf\xb9R\x16X\xad&\xc8lA\xad\x1f\xb8C\xd7\x1bY\x8a\x16\x00\x91\xe9\xfa\x18\xfc+\xc5\xed\xe3\xf1>R\xad\xa5\x82Zy\xee\x9bbc\x98\xae\x16\x14S~\xe2\xb7\xebg\x9f2\xbd\xc7\xe6\x8b\xc2\xcdX\xf8S\xf1\xcc\x92l1S\xb6P\xe17\x00\x00\xff\xffPK\x07\x08\xff\x9c \xd3\xf2\x00\x00\x00p\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00 \x00unary_stream.tmplUT\x05\x00\x01\xe0a\xed\\d\x8f\xc1j\xc30\x0c\x86\xcf\xd5S\xfc\xcba$c\xf8\x01\x06=\x8f^\xbaAv\x1f!QGX\xe3\x18\xd9\xee(F\xef>\xe4t\xf4\xb0\xa3\xfeO\xfe~+\x0c\xe3\xf7\xf0\xc5\x88,\x17\x16\xa2y \xab$\xb4\xb4kXd\x95\xd8\x10P\n&\x9er8T\x18\xe1^\xd7\xf7\xdb;w\xf0!\xa7\x0d\xc0\xbd\xe5t\x9fT\xa9#:e?\xa2\x8d\xe6p=\xcbe\x1e\xf98,\x0c\xd5\xbeVv\x95\xdc\xa2V\xf0d\xe3\xc1\x7f\\\x83\x05\xcf\x88IxXP\x8a\xdb\xb4\xb6\xa9\xea\xfe\xeb>Kq\x1b\xfc\x13\xd7\x03P\x08\x10\x8ex\xd9\xe3\xb1\x14\xfb\xa2\xb9U\x8b\x12l\xc5\xc0V\xe2z\xf6S+\x1c;\x02\xe6S\x85\x0f{\xf8\xf9\\%\xa6IY\xbc\xe5\x04(\xd1\xee\x1e\xac\x12\xdd\x91\x7f\xda\xc6\xaf WN\x98\x97p\xe6\x85}\xe2\xa9\xe9H\xe97\x00\x00\xff\xffPK\x07\x08\x9c\x0cu\xc5\xdf\x00\x00\x00i\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16\x00 \x00unary_stream_test.tmplUT\x05\x00\x01\xe0a\xed\\l\x8e\xcdj\xeb0\x10\x85\xd7\x9a\xa7\x98\xeb\xc5\xc5*A\xd9\x17\xbc)$\x90M\n%/\xa0(cG$\xb6\xdc\xd18$\x08\xbd{\xf1\x0f\xc9\xa2\xdd\xce\x99\xf3}\xa7\xb7\xeeb\x1b\xc2H|#\x06\xf0m\x1fX\xb0\x04U\x08E\xf1]S\x00b\xe1C\x01\xa0\n\x17:\xa1\xbb\x8c\xa7\x94\xf0D\xa7\xa1\xdfM\x85\x88E\xe3\xe5<\x1c\x8d\x0b\xed:\n\x93\xb83\xaf'D\xfdX\xdb\x18\x89\xa5@\xb3\xeb\xfaA\xe6\n\xe6\x0c\x1a\xa0\x1e:\x87\x07\x8a\x92\x12\x9a\xbdm s.\x05\xdf\x16\xbb9hL\xa0\x9c\xdc\xf1\xbd\xc2\xc5o>\xac\xbb4\x1c\x86\xeeTj@d\xfa\x1e\xd3\xff#b\xd7\x1d\x1e\xfd\x08I\x19\x00q\x9cb\xdb\x15\x12\xf3\x04\xb8z\x93\xd2\xe4\xc9\xb9tr_\x8de\x0dj^h\xf6\xfeZ\xca\xf4\xad\x01T\x1dx\x94+\xa6\xf8$\xcc@\xf3E\xeeVjP\xca\xd7SRU\xe8\x83\xd9|n\xa7\x82:2\xd9\x0b(\x95\xe1\xf5\xf2\xaf\xc2\xce_\xe7|\xd1m\xed\xd3g6\xcc\x81K\xad\x7f\xd5\xff\x98\xf6:\x06Y\xeeLQ\x83\xca\x90\xe1'\x00\x00\xff\xffPK\x07\x08\xbeJO\x87\x1b\x01\x00\x00\xd3\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00 \x00unary_unary.tmplUT\x05\x00\x01\xe0a\xed\\D\x8eAj\xc30\x10E\xd7\x9eS|\xb4\x92J\xd0%\xba\xf2&Y\xa4\x170\xf2\xb4\x98\xc6\xb2\x18\x8d\xd2\x04\xa1\xbb\x97\xc8\x86\xac\x86\xff\xe6\xc1\xffi\n\xbf\xd3\x0f#\xb3\xdcY\x88\x965m\xa2\xb04\x18\x16\xd9$\x1b\xa2\xc1\x84-*?\xd4\xd0P+f\x9eK\x1a\xbb\x97\xe1\xc7\x98\x8a\xee \xfeR\xf4\x9dZ#G\xf4]b\x80\xcd\xa8\x15\xfe\xcar_\x02\x9f\xa7\x95\xd1\xda\xb5W\xba\xfe9\x90\x0d\xfa\xc0Q\xe6?\xf7{\x82\xe0\xe3\xe5\x8c\xf1\xeb\x99^\x96\x83\xed\xe0R\xf4 '\xf4\xb1\x0e\x95\x06a-\x12\x11\x97\xdbA\xb3?\xf3\x9f5qS\xc2>\x92\x0b\xd4(\x9e\x90\x98+}\xb7\xfa\xddbd;\xedl\x07\xcd\xebA}\xb6\xf7Vj\xff\xed\xe6\xe583\xc5\x0e\n<\x03\x00\x00\xff\xffPK\x07\x08o\x9c@\x0b\xce\x00\x00\x00,\x01\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x12 \xef\xe40\x01\x00\x00\x16\x02\x00\x00\x12\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00stream_stream.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x89.1q#\x01\x00\x00\xee\x01\x00\x00\x17\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81y\x01\x00\x00stream_stream_test.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x0dG\xdd\xef3\x01\x00\x00\xf7\x01\x00\x00\x11\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xea\x02\x00\x00stream_unary.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\xff\x9c \xd3\xf2\x00\x00\x00p\x01\x00\x00\x16\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81e\x04\x00\x00stream_unary_test.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x9c\x0cu\xc5\xdf\x00\x00\x00i\x01\x00\x00\x11\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xa4\x05\x00\x00unary_stream.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\xbeJO\x87\x1b\x01\x00\x00\xd3\x01\x00\x00\x16\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xcb\x06\x00\x00unary_stream_test.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\xb0d\x95&\xb3\x00\x00\x00\x02\x01\x00\x00\x10\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x813\x08\x00\x00unary_unary.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcNo\x9c@\x0b\xce\x00\x00\x00,\x01\x00\x00\x15\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81- \x00\x00unary_unary_test.tmplUT\x05\x00\x01\xe0a\xed\\PK\x05\x06\x00\x00\x00\x00\x08\x00\x08\x00T\x02\x00\x00G\n\x00\x00\x00\x00" 12 | fs.Register(data) 13 | } 14 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![logo](./docs/logo.png) 2 | -- 3 | 4 | ![Actions Status](https://github.com/lileio/lile/workflows/Test/badge.svg) [![](https://godoc.org/github.com/lileio/lile?status.svg)](http://godoc.org/github.com/lileio/lile) 5 | 6 | Lile is a application generator (think `create-react-app`, `rails new` or `django startproject`) for gRPC services in Go and a set of tools/libraries. 7 | 8 | The primary focus of Lile is to remove the boilerplate when creating new services by creating a basic structure, test examples, Dockerfile, Makefile etc. 9 | 10 | Lile comes with basic pre setup with pluggable options for things like... 11 | 12 | * Metrics (e.g. [Prometheus](https://prometheus.io)) 13 | * Tracing (e.g. [Zipkin](https://zipkin.io)) 14 | * PubSub (e.g. [Google PubSub](https://cloud.google.com/pubsub/docs/overview)) 15 | * Service Discovery 16 | 17 | ### Installation 18 | 19 | Installing Lile is easy, using `go get` you can install the cmd line app to generate new services and the required libraries. First you'll need Google's [Protocol Buffers](https://developers.google.com/protocol-buffers/) installed. 20 | 21 | 22 | ``` 23 | $ brew install protobuf 24 | $ go get -u github.com/lileio/lile/... 25 | ``` 26 | 27 | # Guide 28 | 29 | - [Creating a Service](#creating-a-service) 30 | - [Service Definition](#service-definitions) 31 | - [Generating RPC Methods](#generating-rpc-methods) 32 | - [Running and Writing Tests](#running--writing-tests) 33 | - [Using the Generated cmds](#using-the-generated-cmds) 34 | - [Adding your own cmds](#adding-your-own-cmds) 35 | 36 | ## Creating a Service 37 | 38 | Lile comes with a 'generator' to quickly generate new Lile services. 39 | 40 | Lile follows Go's conventions around `$GOPATH` (see [How to Write Go](https://golang.org/doc/code.html#Workspaces)) and is smart enough to parse your new service's name to create the service in the right place. 41 | 42 | If your Github username was `tessthedog` and you wanted to create a new service for posting to Slack you might use the following command. 43 | 44 | ``` 45 | lile new --name tessthedog/slack 46 | ``` 47 | 48 | Follow the command line instructions and this will create a new project folder for you with everything you need to continue. 49 | 50 | ## Service Definitions 51 | 52 | Lile creates [gRPC](https://grpc.io/) and therefore uses [protocol buffers](https://developers.google.com/protocol-buffers/) as the language for describing the service methods, the requests and responses. 53 | 54 | I highly recommend reading the [Google API Design](https://cloud.google.com/apis/design/) docs for good advice around general naming of RPC methods and messages and how they might translate to REST/JSON, via the [gRPC gateway](https://github.com/grpc-ecosystem/grpc-gateway) 55 | 56 | An example of a service definition can be found in the Lile example project [`account_service`](https://github.com/lileio/account_service) 57 | 58 | ``` protobuf 59 | service AccountService { 60 | rpc List (ListAccountsRequest) returns (ListAccountsResponse) {} 61 | rpc GetById (GetByIdRequest) returns (Account) {} 62 | rpc GetByEmail (GetByEmailRequest) returns (Account) {} 63 | rpc AuthenticateByEmail (AuthenticateByEmailRequest) returns (Account) {} 64 | rpc GeneratePasswordToken (GeneratePasswordTokenRequest) returns (GeneratePasswordTokenResponse) {} 65 | rpc ResetPassword (ResetPasswordRequest) returns (Account) {} 66 | rpc ConfirmAccount (ConfirmAccountRequest) returns (Account) {} 67 | rpc Create (CreateAccountRequest) returns (Account) {} 68 | rpc Update (UpdateAccountRequest) returns (Account) {} 69 | rpc Delete (DeleteAccountRequest) returns (google.protobuf.Empty) {} 70 | } 71 | ``` 72 | 73 | ## Generating RPC Methods 74 | 75 | By default Lile will create a example RPC method and a simple message for request and response. 76 | 77 | ``` protobuf 78 | syntax = "proto3"; 79 | option go_package = "github.com/tessthedog/slack"; 80 | package slack; 81 | 82 | message Request { 83 | string id = 1; 84 | } 85 | 86 | message Response { 87 | string id = 1; 88 | } 89 | 90 | service Slack { 91 | rpc Read (Request) returns (Response) {} 92 | } 93 | ``` 94 | 95 | Let's modify this to be a real service and add our own method. 96 | 97 | We're going to create an `Announce` method that will announce a message to a Slack room. 98 | 99 | We're assuming that the Slack team and authentication is already handled by the services configuration, so a user of our service only needs to provide a `room` and their `message`. The service is going to send the special `Empty` response, since we only need to know if an error occurred and don't need to know anything else. 100 | 101 | Our `proto` file now looks like this... 102 | 103 | ``` protobuf 104 | syntax = "proto3"; 105 | option go_package = "github.com/tessthedog/slack"; 106 | import "google/protobuf/empty.proto"; 107 | package slack; 108 | 109 | message AnnounceRequest { 110 | string channel = 1; 111 | string msg = 2; 112 | } 113 | 114 | service Slack { 115 | rpc Announce (AnnounceRequest) returns (google.protobuf.Empty) {} 116 | } 117 | ``` 118 | 119 | We now run the `protoc` tool with our file and the Lile method generator plugin. 120 | 121 | ``` 122 | protoc -I . slack.proto --lile-server_out=. --go_out=plugins=grpc:$GOPATH/src 123 | ``` 124 | 125 | Handily, Lile provides a `Makefile` with each project that has a `proto` build step already configured. So we can just run that. 126 | 127 | ``` 128 | make proto 129 | ``` 130 | 131 | We can see that Lile will create two files for us in the `server` directory. 132 | 133 | ``` 134 | $ make proto 135 | protoc -I . slack.proto --lile-server_out=. --go_out=plugins=grpc:$GOPATH/src 136 | 2017/07/12 15:44:01 [Creating] server/announce.go 137 | 2017/07/12 15:44:01 [Creating test] server/announce_test.go 138 | ``` 139 | 140 | Let's take a look at the `announce.go` file that's created for us. 141 | 142 | ``` go 143 | package server 144 | 145 | import ( 146 | "errors" 147 | 148 | "github.com/golang/protobuf/ptypes/empty" 149 | "github.com/tessthedog/slack" 150 | context "golang.org/x/net/context" 151 | ) 152 | 153 | func (s SlackServer) Announce(ctx context.Context, r *slack.AnnounceRequest) (*empty.Empty, error) { 154 | return nil, errors.New("not yet implemented") 155 | } 156 | ``` 157 | 158 | We can now fill in this generated method with the correct implementation. But let's start with a test! 159 | 160 | ## Running & Writing Tests 161 | 162 | When you generate an RPC method with Lile a counterpart test file is also created. For example, given our `announce.go` file, Lile will create `announce_test.go` in the same directory. 163 | 164 | This should look something like the following.. 165 | 166 | ``` go 167 | package server 168 | 169 | import ( 170 | "testing" 171 | 172 | "github.com/tessthedog/slack" 173 | "github.com/stretchr/testify/assert" 174 | context "golang.org/x/net/context" 175 | ) 176 | 177 | func TestAnnounce(t *testing.T) { 178 | ctx := context.Background() 179 | req := &slack.AnnounceRequest{} 180 | 181 | res, err := cli.Announce(ctx, req) 182 | assert.Nil(t, err) 183 | assert.NotNil(t, res) 184 | } 185 | 186 | ``` 187 | 188 | You can now run the tests using the `Makefile` and running `make test`... 189 | 190 | ``` 191 | $ make test 192 | === RUN TestAnnounce 193 | --- FAIL: TestAnnounce (0.00s) 194 | Error Trace: announce_test.go:16 195 | Error: Expected nil, but got: &status.statusError{Code:2, Message:"not yet implemented", Details:[]*any.Any(nil)} 196 | Error Trace: announce_test.go:17 197 | Error: Expected value not to be nil. 198 | FAIL 199 | coverage: 100.0% of statements 200 | FAIL github.com/tessthedog/slack/server 0.011s 201 | make: *** [test] Error 2 202 | 203 | ``` 204 | 205 | Our test failed because we haven't implemented our method, at the moment we're returning an error of "unimplemented" in our method. 206 | 207 | Let's implement the `Announce` method in `announce.go`, here's an example using `nlopes`' [slack library](https://github.com/nlopes/slack). 208 | 209 | ``` go 210 | package server 211 | 212 | import ( 213 | "os" 214 | "google.golang.org/grpc/codes" 215 | "google.golang.org/grpc/status" 216 | 217 | "github.com/golang/protobuf/ptypes/empty" 218 | "github.com/tessthedog/slack" 219 | sl "github.com/nlopes/slack" 220 | context "golang.org/x/net/context" 221 | ) 222 | 223 | var api = sl.New(os.Getenv("SLACK_TOKEN")) 224 | 225 | func (s SlackServer) Announce(ctx context.Context, r *slack.AnnounceRequest) (*empty.Empty, error) { 226 | _, _, err := api.PostMessage(r.Channel, r.Msg, sl.PostMessageParameters{}) 227 | if err != nil { 228 | return nil, status.Errorf(codes.Internal, err.Error()) 229 | } 230 | 231 | return &empty.Empty{}, nil 232 | } 233 | ``` 234 | 235 | Let's fill out our testing request and then run our tests again... 236 | 237 | ``` go 238 | package server 239 | 240 | import ( 241 | "testing" 242 | 243 | "github.com/tessthedog/slack" 244 | "github.com/stretchr/testify/assert" 245 | context "golang.org/x/net/context" 246 | ) 247 | 248 | func TestAnnounce(t *testing.T) { 249 | ctx := context.Background() 250 | req := &slack.AnnounceRequest{ 251 | Channel: "@alex", 252 | Msg: "hellooo", 253 | } 254 | 255 | res, err := cli.Announce(ctx, req) 256 | assert.Nil(t, err) 257 | assert.NotNil(t, res) 258 | } 259 | ``` 260 | Now if I run the tests with my Slack token as an `ENV` variable, I should see a passing test! 261 | 262 | ``` 263 | $ alex@slack: SLACK_TOKEN=zbxkkausdkasugdk make test 264 | go test -v ./... -cover 265 | ? github.com/tessthedog/slack [no test files] 266 | === RUN TestAnnounce 267 | --- PASS: TestAnnounce (0.32s) 268 | PASS 269 | coverage: 75.0% of statements 270 | ok github.com/tessthedog/slack/server 0.331s coverage: 75.0% of statements 271 | ? github.com/tessthedog/slack/slack [no test files] 272 | ? github.com/tessthedog/slack/slack/cmd [no test files] 273 | ? github.com/tessthedog/slack/subscribers [no test files] 274 | ``` 275 | 276 | ## Using the Generated cmds 277 | 278 | Lile generates a cmd line application based on [cobra](https://github.com/spf13/cobra) when you generate your service. You can extend the app with your own cmds or use the built-in cmds to run the service. 279 | 280 | Running the cmd line app without any arguments will print the generated help. 281 | 282 | For example `go run orders/main.go` 283 | 284 | ### up 285 | Running `up` will run both the RPC server and the pubsub subscribers. 286 | 287 | ```go run orders/main.go up``` 288 | 289 | ## Adding your own cmds 290 | 291 | To add your own cmd, you can use the built in generator from [cobra](https://github.com/spf13/cobra) which powers Lile's cmds 292 | 293 | ``` bash 294 | $ cd orders 295 | $ cobra add import 296 | ``` 297 | 298 | You can now edit the file generated to create your cmd, `cobra` will automatically add the cmd's name to the help. 299 | -------------------------------------------------------------------------------- /statik/statik.go: -------------------------------------------------------------------------------- 1 | // Code generated by statik. DO NOT EDIT. 2 | 3 | // Package statik contains static assets. 4 | package statik 5 | 6 | import ( 7 | "github.com/rakyll/statik/fs" 8 | ) 9 | 10 | func init() { 11 | data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00 \x00Dockerfile.tmplUT\x05\x00\x01\xe0a\xed\\t\x90\xc1O\xc20\x14\xc6\xef\xfd+^v\xe0dW\x8e\xc6d\x07\x04$DY\xc9\xc0\x18\x82\xc4\x94\xb6\x94\xc5\xadm\xdaNM\x96\xfd\xeffs\x01\x85x\xfb\xde\xfb\xfa\xbd\xfe\xde{\xc8\xe8\x02\x94)\x98Vw\xac\xb0\xb9\x960Z\xc1\xbe\xca\x0b\x81\xa5\xfe@/4{\x9c\xcc3 u\x0dq\xcaJ M\x83\xb2\xe7\x14\x98}\x87\xca\n\x16$\x0c\x06}\xa5\x1c\x13]\xf9\x8a\x00\xa0k2!\x00cm0g\xfc(a\xcf\xfc\x11T\x1e\xc0X\xa9\xbd?\xa21]n@\x99\xb84\xe2\xcf\x1f\xe4\xa7w\xf2}U^\xf9\xbe*;\x14e\xa0\x8d\x0b\xf3\xa9\x0b\xc3\xfaL|\x8d<\x9e\xd1\xb7i:\xba\x7f\x9aN\x92!\xcc(]%E\xae\xab\xafv@\xb71\xe0^\x90\xba\xee\x92M\x03\xf1Y#\x84\xbasy\xeeX\xe0=;\xc6\x07g\xca\xe4t1 2p\xe2}A\xb8t\xc1\x13\xcep+\xf2C\xceY\x90>\xe6.\\>\xf9o\xd0\xefu/\xb1\x08\x9a\xa6\xebl\xb3\xa4\xf3t\x0d\xdb\xe8\xecD;4^L`\x1bU6\xba\x81\x08c\xe5,\xc7\xd6\xb8\x90\xdc\x0e\xa3\x1d\xfa\x0e\x00\x00\xff\xffPK\x07\x08f\x96,\xd5#\x01\x00\x00\xf1\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00 \x00Makefile.tmplUT\x05\x00\x01\xe0a\xed\\,\x8e1k\xc30\x10F\xe7\xdc\xaf8\xe8\xda;\xd3\xd5\xe0\xbd]\xd2\xae\x9d\x82b.\x17Q\xd9'\xa4\x93\x97\x90\xff^,g{\x0f>>\xde\x1bnq\xc4\x9bOK\xf8\x13\xe0\x9f\xcf\xef\xf3\xef\x88\xb9\x98\x1b\xbaT\x07\xe8<\xc2I\x0dU\x1c5\xfa\xbd]y\xb6ePKa\xd5\xa1\x0f\xae\xedv\xc0L*+\xa9\xc1\xe9P\xa4/d|<\xf8\x1c\x16y>\xf9\xb8&J1 U)\x9b\x94\x8b5\x9f\x18\x89\xd4:\xe6\xd44\xaeu\xd2\x92\xe7\xf7\x1c\xfc^\xa7j\xad\xccr)\x92\x82\xc7MF\x06\xd8\xeb^\xa5=nw\xa4\x8c\x1fH\x1b\xf2\xc0\xcc\xf0\x1f\x00\x00\xff\xffPK\x07\x08\x1e\x06\xed\xa9\xa8\x00\x00\x00\xdd\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x19\xa89O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00 \x00client.tmplUT\x05\x00\x01\x83\xd5\x8b]|SAk\xe3<\x10=K\xbfb>\x1f\xbe\xb5!\x95a\x8f\x0b9\xa5l)t\xb3\xd0n\xe8\xb1\xa8\xf2\xc4\x16\x95g\xccXI\x1aB\xfe\xfb\"\xdb\xdbu\xbb4\xbaX\x9e\x99\xf7\xf4\xfc\xf4\xdcY\xf7bk\x84\xd3 \xcc\xda\xb6\x08\xe7\xb3\xd6\xbe\xedX\"\xe4Ze\xfd\x91\\\xa6\xb5\xcaj\x1f\x9b\xdd\xb3q\xdc\x96\xb5t\xee\n\x1d\xf7\xc7>\xe2\xf4\xca\x1dR\x14\xeb<\xd5e\xcd%\xc7T\xcd\xde\xe3\x82\x0f\xe8yx\x94\xfb\xaf\x99V3\x10\xcc\x07\xe7d\xb3\xfdU\xcd\x99\x86i%\xfe\xa7\xd6WU\xc0\x83\x15\x84K\x02\xf9j\xa8\xfc\x9d\x1e\x841\xd7\x01M\xcd\xc1RmX\xear\x94\\h\xbd\xb7\x92>\xde\xb5\xb0\x84\xff\x93\x03\xe6\xc7.\xe2\xeb\xe9\xac\xd5*x\xa48\xd8\xb5\xb2-\x86\x95\xedq\xf2ml%\xfcvG\x0en0~>\x95\x17\x17(\xe0\x94\xce6w\xec^\xf2B\xab\n\xb7(\xe0Z\xb3\xa10\x96\xb4\xf2[\x98f\xff[\x02\xf9\x90 J0\xee\x84\xa6\x86Vg\xadU\x8f\xb2\xf7\x0e7\xf7w\xf0m \xc9y\xb3\xb9\xbf\xfb\xce\xf206\xf2,\xc9\xb8^?L\x02\xb2D^\x96\xf0\x88P1}\x89@\x88\x15D\x06\x14a\x81\x06\x05\x17`{\x88\x8d\xef\xc1 \xda\x88=X\xe8\x98\x03X\xaa\xc01\x11\xba\xe8\x99\xfa\x81\xe7\xe0C\x80\xc6v\x1d\x12\x04\x1bQ\xb4J#\x0bxJz\x92\xe1\xe6\xda\xdb\x90k5\x93\xba\xd0J\x0d\xadG\x1f\x9b[\xea\xd1\xed\x04\xf3\xe2]yCV\x8e\xb7\x14Q\x1cv\x91%\x7f\x0b\xc6\xc7\xf5!(f\xd5XO\x03|\xba\x8aO\x91i\x0d\x96\xad\x98\"\xbe\xc6q~~h\xb1\xb8\x08\x1e\xff\x02\xf3\xb3C\xfa5F\xf8_\x8aY\xbe\xcdM\xe0g\x1b\xd2(J^\\ \x1f\xbc(\xd2]\xb9\xe0\x93\x93k<\\\x08[\xb2\xbcx\xcb\xee\x12\\\xf0\xfaOZ\xd2\xfe\xac\x7f\x07\x00\x00\xff\xffPK\x07\x08\xe4\xe99\xbd\xdf\x01\x00\x00\x07\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x1d\xa89O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00 \x00cmd_main.tmplUT\x05\x00\x01\x8a\xd5\x8b]t\x91\xc1\x8e\xdb \x10\x86\xcf\xccS\x8c|XA\x15\x81\xd4c\xa4\x9e\xa2m/\xddj\xd5<@\x85\xf1\x84E\x05cap+E~\xf7\n\xe2l\xf7\xe0\x9cl\xfe\x8f\xf9\xff\x19f\xd2\xe6\xb7\xb6\x84A\xbb\x11\xc0\x85)\xa6\x8c\x1c\xd8/\xecF\xca\xea-\xe7IMS\x8a\x97\x0e\x80u\xd6\xe5\xb7\xd2K\x13\x83\xf2\xce\x93\x8b\xed\xa3\x96\xcf\xdd>\x8c6\xed\x93K\x8a\x81\xc6e\x1fN\xa5\x9fK\xff\xd0\xf5\x1d\xab\xe0\x86\xc1\xd3\x1f\x9dH\x0dt\xd1\xc5\xe7\xb9\x95\xc4h=I\x1b\xbd\x1e\xad\x8c\xc9*\x9b&S\xc9\xf5\x8a\xf2%\x0e\xc5\xd3\x0f\x1d\x08\xd7uWT3\xa5\x85\xd2>\xab\xca\xfd\xdf\x84\xa1\x03\x01p)\xa3i/\xc8\x05^\x81\xd5\xb1\xe5\x99\xf2wZ\xc8\x7fM1<\x8f\x0b\x17\xc0f<~\xc1\xa7\x9b\xb9\xac>'\x1d\xc8\x9f\xf4|7?7t]\x01X\x9d\xb5\xe5\xf0\xeeCb'6r\xbb\xc9k0\xb7\xf8\xa9\xce\xb7i\xad\x03\xf6\xa1F\xfe$\xeb\xe6L\xe9q\"\xb7\x07\x9c\x05\xb0U\x00\xb0\xdb\xf3\xd6\xfeO\xde\xd1\x98\xf9\xd3\xa6\xdc\x8e\xd5\xbdV9\xd3<\x8e\xd8\xfa\xf9\xe6c\xaf\xfd\xa6s\xd1\xb2\x0f\xc0\xd8k\x8a\x8b\x1b(\x1d\x11\x11\xb7\xa5\xcb\xd7\xd2\x9fK\x7fG\\\xd4\x8b/\xef\xab<\"\xde\x97)\xff\xab\x87\xad;\x13\x06\xf9\xfc\x97L\xc9\xc4\x05\xac\xf0/\x00\x00\xff\xffPK\x07\x08\xf2\xc9d\xa2[\x01\x00\x00\xc0\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\"\xa89O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00 \x00cmd_root.tmplUT\x05\x00\x01\x91\xd5\x8b]<\x8cMj\xc30\x10F\xd7\x9aSLgeC\xab\xd0.[\xbchM\xba,!7P\xe4\x91:T?A\x92M\xc0\xf8\xee%)\xcd\xea\x83\xef=\xde\xd9\xd8\x1f\xe3\x19m\x9c\x00$\x9esi\xd8\x81\"\x17\x1b\x81\xa2\\ @\x91\x97\xf6=\x9f\xb4\xcdq\x17$\xb0\xe4\xdb\xec\x96\x17\x82\x1e`1\x05\xad\xf3\x9f\x12\x18k+\x92\xfc\xdfw\xcc\xb9\x8dq\xc2\x01\xaf\xb6\xfe0\x95\xc7\x1c\xa3ISG\xeb\x8a\xfa\xcbD\xc6m\xa3G\xa4w\xf4\xc7\xc3\x88'Sy\xc2\xcae\x11\xcb\xd4\x03\xb89Y\xdc_\xd8\xce\x8d\xbb\x1eWP\xe2\x90K\xc1\xd7\xe1?\xaf\xef\xf8\xedF\x1e\x06L\x12\xae\xaar\xb1\xe9C\x91\xd4B\xea\xb8\x94\x1e\x94\xcaU\xef/\xd2\xba\xa7\xe7\x1e\xd4\x06\x1b\xfc\x06\x00\x00\xff\xffPK\x07\x08w+\x85(\xca\x00\x00\x00\x01\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00$\xa89O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00 \x00cmd_up.tmplUT\x05\x00\x01\x95\xd5\x8b]l\x91\xbf\x8e\xdc \x10\xc6k\xe6)F.Nv\xe4`]\xd2\xadrEdE\xd1\x15w\x8a\xec\xa4\x8aR``mt\x06,\x066:Y~\xf7\x88\xf5n\xb4\xf9C\x01\x1af\xe6\x9b\x8f\x1f\x8b\x90/b\xd4(\xad\x020v\xf1!b xY\x85\xa7\x02X\xe1\xa9!3:1\xe7\x80^I\x8ay.\x00X1\x9a8\xa5\x81Ko\x9b\xd9\xcc\xda\xf8fI\x03\xa5\xa19\xbd+\xfe\x9b\xce\xc7\xbfIZ\x8e\xf7\xef\x1b\xe9\x87 rf]\x91?y\x95f\xfd,\xac\xc6mk(\x0d$\x83\x19t\xa0\x02*\x80\x93\x08\x98\x96\xd6*|\xc0\xbbs\x1fo\xbd\xb5\xc2\xa9\x15\xd87\xd2\x87\xec=-E\x0d\xac\x9f|\x88\x87\x1caH\x8ep\xf0q\xc2\xeeK\x8b\xa4\xc3\xc9H\x9dk\xba\xe4\x0exLN\x96\xd2*|\xf3\x87`\x8d\"\x8c\x84\xdf\x7fP\x0c\xc6\x8d\x15\xae\xc0\x98\xc4\xc3\x03Z\xf1\xa2K9 \x87\x9ex\x7f\xe6S\xe3}\x05\x8c\xed\xb0\xf8\xb3\x8f\xe6\xf8Z\xca:\x17<\xba\xa8CHK\xac\xf1B\x90\xf7\x8f\x9f\xbf~\xea\x9e*\x00\xc6F\xbf\x1b\xd8\xf5Y\xe6\xc4\xbb\xe4\xca,\xb7\x9d\xf7\xbf*v\xd0\xbc\xbf\x92)\xefn \xf1\x8c\xb0\x15V\xcf\xad\xa0+\xc5~\x7f\xf0\xef\x8e\xb0nWu`\xec\xc3[ \x97\xb9\xfd\x94\xa2\xf2?\xf7\xe1\xd797w[\x0d\x1b@6\x83\xc6\x99\xb8;\xea\xbc\x8f\xadU\xfc\xa3R\x17r\xe5\xf9\x87*\xd8\xe0W\x00\x00\x00\xff\xffPK\x07\x08\xd8\xec\xc5\xadr\x01\x00\x00e\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00gitignore.tmplUT\x05\x00\x01\xe0a\xed\\J*\xcd\xccI\xd1\xe7\xd2s \x8e\x0f.\xc9/J\xe5\x02\x04\x00\x00\xff\xffPK\x07\x08\xe4\xa5\xd4\x89\x17\x00\x00\x00\x11\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00+\xa89O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00 \x00go-mod.tmplUT\x05\x00\x01\xa3\xd5\x8b]\xca\xcdO)\xcdIU\xa8\xaeV\xd0\xf3\x053\xfd\x12sS\x15jk\xb9\xb8\xd2\xf3\x15\x0c\xf5\x0c\x8d\xb9\x00\x01\x00\x00\xff\xffPK\x07\x08\xffkCw%\x00\x00\x00\"\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00 \x00proto.tmplUT\x05\x00\x01\xe0a\xed\\t\x8e\xb1\xca\xc20\x14\x85\xf7\xfb\x14\x87N\xed\xf2\xc3\x8fc\xe8\xd4\xc1I\x07_@B{)\xc16\x89\xb9\xa9(!\xef.Q\x8b\"\xb8\x9e\xef\xdc\xf3]\xb9\xd9\xa8\xafhQ\xf9\xe0\xa2\xdbT\x8a\x9c\x8f\xc6Y\x8c\xee\xe8u\x7f\xd2#\x17\x9a\x12\xfevnX&\xde\xeb\x99\x91s\xa5h\xc5\x85\xbdRE4\xb3HI\xb7\x1c\x0f|^X\"\x12\x01\x12\x83\xb1#\xcc\x80\x16\xff\x8a\xf2WQ\xbc\xb3\xc2?\x9a\xc2\xe1b\xfa\xa7\xa8\xd33O\x9d\x96\xf5\x8f\xc7I\xf0}\x99\xa9\xdf\xce\x06\x81\xe3\x12\xac\xa0\xfe\xd8o\x902e\xba\x07\x00\x00\xff\xffPK\x07\x08\x84\x01\x0dr\x9e\x00\x00\x00\xf5\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00 \x00server.tmplUT\x05\x00\x01\xe0a\xed\\*HL\xceNLOU(N-*K-\xe2\xe2\xca\xcc-\xc8/*Q\xd0\xe0\xe2T\xaa\xaeV\xd0\xf3\xcdO)\xcdI\xf5K\xccMU\xa8\xadU\xe2\xd2\xe4\xe2\xe2*\xa9,HU\x00\xc99'\xe6\xa6\xe68'\x16\xc3\xa4\x83\xc1F(\x14\x97\x14\x95&\x97(Tsq\x82\x14A\xe5\xf4pk\xe0\xaa\xe5\x02\x04\x00\x00\xff\xffPK\x07\x08\xa1\x1b\xbf\x91a\x00\x00\x00\x85\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x003\xa89O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00 \x00server_test.tmplUT\x05\x00\x01\xb2\xd5\x8b]t\x90\xb1j\xc30\x10\x86g\xddS\x1c\x9e\xa4\x10\x14\xe8X\xc8d:&C\xda\x17P\x9d\xebUT\x96\x8c$'\x05\xe3w/g'\xd0B3i\xf8?}\xf7\xdf\x0d\xae\xfbrLX(_(\x03\xf8~H\xb9\xa2\x06\xd5\xa4\xd2\x80j*\x95\xea#7\x00\xaa\xe1\x948\x90\xe5\x14\\d\x9b2\xef8\x0f\xdd\x1a\xf9\xfa9\xbe\xdb.\xf5\xbb\xe0\x03\xf9\xb4<\xbb\xcb\x938\xa6 \xed!\x9d\xc7@G\xd7\x13\xces\x03\x06\xe0\xe22\x16\xdc\xa3\xa4\xad\xeb)\xb4\xae\xdc\x81\xd7\xa5\xce4/P\x17\xfc\x02\xdd2\xfb\xdf\x876x\x8a\x15\xe0c\x8c\x1d\xbeQ\xa9\x07\xe7\xa3\xeeqs\xebo\x0f\x06'P\xbe\x1f\x02>\xefQ0\xcd\xb8\x91\xfev\x1d\xb6\xe4\xea\xf7\x9c\x13\xb1/\x95\xf2\xe3\x82\x9a\xb7X\x0c\xa8\x19@q\x11\xf1\"<\xd2\xf5\x96\x9bu\xa4\xe6b\x00\x94;\x9f\xf3v=\xb5\xb0r!a\xa5\xee\xdd'6N+\xa3\xe5\x8f,\xbf\xff\xb3\xfe\x91\xae\x8f/\xa0\x17\xa9\x18\xdb\x14\xa3\x96\x89F4\xa9\xd8\x97o_uoOc\xd4\xc6\xc0\x0c?\x01\x00\x00\xff\xffPK\x07\x08\xc5\x07\xd0\x9d\x18\x01\x00\x00\xfb\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x005\xa89O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00 \x00subscribers.tmplUT\x05\x00\x01\xb6\xd5\x8b]\x94\x911\x8f\xdb0\x0c\x85\xe7\xe8W\x10\xeeb\x07\x81\xd4v\xf4vp\x87.i\x06w\x0fd\x85u\x84\xb3D\x95\xa2\x0ew0\xfc\xdf\x0b\x9f}\xd9\n\xb4\x93@>\xbe\xc7\x0fb\xb2\xee\xd9\x8e\x08\xb9\x0c\xd9\xb1\x1f\x90\xb3R>$b\x81Z\x1d\xaa\xd1\xcb\xbd\x0c\xdaQ0\x93\x9f\xd0\x93Ie\xc8e0/_+\xd5(%o a\x9eAw6\xe0\xd4\xd9\x8c?l@X\x96\x1e\xf9\xc5;\xec\x1f\xb9\x90\x85\x8b\x13\x98\x17\xa5~\x95\xe8\xa0\xcep\xfc'g\x03=JI\xb5\x83\xe3\xb6\\w\x93\xc7(\x0d\xcc\xea`\x0c\xdcERn\x8d\x19\xe9FN\x13\x8f\xe6o\xd4\x9f6\xa3\xbe\xc4w\xa3\xd3\x97X\xef\x91\xdfm\xbcM\xc8\x97$\x9eb\xde\x82\x0f?)y\xd7\x02\x00T\x99\x02^e\xad\xab\xd3&\xae\xb8\xef\x1aTyC\xbeF\x1b\xf0C\xde\x03[\x80\xac{\nxF\xb9\xd3m\x17\xbf\xa1\xbdM>b\x0b_>\xc3\x11\xc4\x07\xd4=:\x8a\x1f\x03\x1dEW\x981\xba\xb7uf\xef>\x15\xa1'\xf7\xdc\x02\x08\x17\xdc\x9aK\xa3\x16\xa5\x8c\x81\xff\xfe\xd4\x07T\xed\xe4\x15\x1cE\xc1W\xd1\xdd\xf6\x9e\x80\xf17\x1c\x13\x93\x90>c\xcev\xc4\x13\\\x1f'8\xe7\xb1\x01d&\x86y\xdd~`\x94\xc2\x11\xa2\x9f\xd6rQ\x7f\x02\x00\x00\xff\xffPK\x07\x08:\x0c!BM\x01\x00\x00Z\x02\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcNf\x96,\xd5#\x01\x00\x00\xf1\x01\x00\x00\x0f\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00Dockerfile.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x1e\x06\xed\xa9\xa8\x00\x00\x00\xdd\x00\x00\x00\x0d\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81i\x01\x00\x00Makefile.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x19\xa89O\xe4\xe99\xbd\xdf\x01\x00\x00\x07\x04\x00\x00\x0b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81U\x02\x00\x00client.tmplUT\x05\x00\x01\x83\xd5\x8b]PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x1d\xa89O\xf2\xc9d\xa2[\x01\x00\x00\xc0\x02\x00\x00\x0d\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81v\x04\x00\x00cmd_main.tmplUT\x05\x00\x01\x8a\xd5\x8b]PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\"\xa89Ow+\x85(\xca\x00\x00\x00\x01\x01\x00\x00\x0d\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x15\x06\x00\x00cmd_root.tmplUT\x05\x00\x01\x91\xd5\x8b]PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00$\xa89O\xd8\xec\xc5\xadr\x01\x00\x00e\x02\x00\x00\x0b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81#\x07\x00\x00cmd_up.tmplUT\x05\x00\x01\x95\xd5\x8b]PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\xe4\xa5\xd4\x89\x17\x00\x00\x00\x11\x00\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd7\x08\x00\x00gitignore.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00+\xa89O\xffkCw%\x00\x00\x00\"\x00\x00\x00\x0b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x813 \x00\x00go-mod.tmplUT\x05\x00\x01\xa3\xd5\x8b]PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\x84\x01\x0dr\x9e\x00\x00\x00\xf5\x00\x00\x00\n\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x9a \x00\x00proto.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xaa\x83\xbcN\xa1\x1b\xbf\x91a\x00\x00\x00\x85\x00\x00\x00\x0b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81y\n\x00\x00server.tmplUT\x05\x00\x01\xe0a\xed\\PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x003\xa89O\xc5\x07\xd0\x9d\x18\x01\x00\x00\xfb\x01\x00\x00\x10\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1c\x0b\x00\x00server_test.tmplUT\x05\x00\x01\xb2\xd5\x8b]PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x005\xa89O:\x0c!BM\x01\x00\x00Z\x02\x00\x00\x10\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81{\x0c\x00\x00subscribers.tmplUT\x05\x00\x01\xb6\xd5\x8b]PK\x05\x06\x00\x00\x00\x00\x0c\x00\x0c\x00.\x03\x00\x00\x0f\x0e\x00\x00\x00\x00" 12 | fs.Register(data) 13 | } 14 | -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/lileio/lile/master/lile.png) 2 | 3 | > **ALPHA:** Lile当前正处于Alpha版本,可能还会存在一些变化。目前,我正在收集反馈意见,并将尽快敲定Lile,以避免发生变化。 4 | 5 | Lile是可以帮助您快速创建基于gRPC通讯,或者可以通过[gateway](https://github.com/grpc-ecosystem/grpc-gateway)创建REST通讯的发布订阅服务的一个生成器和工具集。 6 | 7 | Lile主要是用于过创建基本结构,测试示例,Dockerfile,Makefile等基础骨架。 8 | 9 | Lile也是一个简单的服务生成器,扩展了基本的gRPC服务器,包括诸如指标(如[Prometheus](prometheus.io)),跟踪(如[Zipkin](zipkin.io))和发布订阅(如[Google PubSub](https://cloud.google.com/pubsub/docs/overview)等可插拔选项。 10 | 11 | Lile在的Slack上的[Gopher Slack](https://invite.slack.golangbridge.org/) 交流渠道[#lile](https://gophers.slack.com/messages/C6RHLV3LN) 12 | 13 | [![Build Status](https://travis-ci.org/lileio/lile.svg?branch=master)](https://travis-ci.org/lileio/lile) [![GoDoc](https://godoc.org/github.com/lileio/lile?status.svg)](https://godoc.org/github.com/lileio/lile) [![Go Report Card](https://goreportcard.com/badge/github.com/lileio/lile)](https://goreportcard.com/report/github.com/lileio/lile) [![license](https://img.shields.io/github/license/mashape/apistatus.svg)]() 14 | 15 | [![asciicast](https://asciinema.org/a/rLAGV6nsdBreyWgXtb6bgL3Hb.png)](https://asciinema.org/a/rLAGV6nsdBreyWgXtb6bgL3Hb) 16 | 17 | ### 安装 18 | 19 | 安装Lile很容易,使用`go get`便可以安装Lile的命令行工具来生成新的服务和所需的库。 20 | 21 | ``` 22 | $ go get -u github.com/lileio/lile/... 23 | ``` 24 | 25 | 您还需要安装Google的[Protocol Buffers](https://developers.google.com/protocol-buffers/)。 26 | 27 | ### 入门 28 | 29 | 通过执行`lile run`和一个短文件路径生成一个新的服务。 30 | 31 | Lilek可以自动根据`username/service`生成一个完整的路径到`$GOPATH`下的`github.com`中。 32 | 33 | ``` 34 | $ lile new lileio/users 35 | ``` 36 | 37 | # 指南 38 | 39 | - [安装](#安装) 40 | - [创建服务](#创建服务) 41 | - [服务定义](#服务定义) 42 | - [生成RPC方法](#生成RPC方法) 43 | - [编写并运行测试](#编写并运行测试) 44 | - [使用生成的命令行](#使用生成的命令行) 45 | - [自定义命令行](#自定义命令行) 46 | - [暴露Prometheus指标](#暴露Prometheus采集指标) 47 | - [发布和订阅](#发布和订阅) 48 | - [发布事件](#发布事件) 49 | - [自动发布事件](#自动发布事件) 50 | - [订阅事件](#订阅事件) 51 | - [追踪](#追踪) 52 | 53 | ## 安装 54 | 55 | 首先,你需要确保您在您安装Lile之前已经安装了Go。 56 | 57 | 安装Lile很容易,使用`go get`便可以安装Lile的命令行工具来生成新的服务和所需的库。 58 | 59 | ``` 60 | $ go get github.com/lileio/lile/... 61 | ``` 62 | 63 | 您还需要安装Google的[Protocol Buffers][Protocol Buffers](https://developers.google.com/protocol-buffers/)。 64 | 65 | 在MacOS你可以使用`brew install protobuf`来安装。 66 | 67 | ## 创建服务 68 | 69 | Lile使用生成器来快速生成新的Lile服务。 70 | 71 | Lile遵循Go关于$GOPATH的约定(参见[如何写Go](https://golang.org/doc/code.html#Workspaces)),并且自动解析您的新服务的名称,以在正确的位置创建服务。 72 | 73 | 如果您的Github用户名是lileio,并且您想创建一个新的服务为了发布消息到Slack,您可以使用如下命令: 74 | 75 | ``` 76 | lile new lileio/slack 77 | ``` 78 | 79 | 这将创建一个项目到`$GOPATH/src/github.com/lileio/slack` 80 | 81 | ## 服务定义 82 | 83 | Lile服务主要使用gRPC,因此使用[protocol buffers](https://developers.google.com/protocol-buffers/)作为接口定义语言(IDL),用于描述有效负载消息的服务接口和结构。 如果需要,可以使用其他替代品。 84 | 85 | 我强烈建议您先阅读[Google API设计](https://cloud.google.com/apis/design/)文档,以获得有关RPC方法和消息的一般命名的好建议,以及如果需要,可以将其转换为REST/JSON。 86 | 87 | 您可以在Lile中发现一个简单的例子[`account_service`](https://github.com/lileio/account_service) 88 | 89 | ``` protobuf 90 | service AccountService { 91 | rpc List (ListAccountsRequest) returns (ListAccountsResponse) {} 92 | rpc GetById (GetByIdRequest) returns (Account) {} 93 | rpc GetByEmail (GetByEmailRequest) returns (Account) {} 94 | rpc AuthenticateByEmail (AuthenticateByEmailRequest) returns (Account) {} 95 | rpc GeneratePasswordToken (GeneratePasswordTokenRequest) returns (GeneratePasswordTokenResponse) {} 96 | rpc ResetPassword (ResetPasswordRequest) returns (Account) {} 97 | rpc ConfirmAccount (ConfirmAccountRequest) returns (Account) {} 98 | rpc Create (CreateAccountRequest) returns (Account) {} 99 | rpc Update (UpdateAccountRequest) returns (Account) {} 100 | rpc Delete (DeleteAccountRequest) returns (google.protobuf.Empty) {} 101 | } 102 | ``` 103 | 104 | ## 生成RPC方法 105 | 106 | 默认情况下,Lile将创建一个RPC方法和一个简单的请求和响应消息。 107 | 108 | ``` protobuf 109 | syntax = "proto3"; 110 | option go_package = "github.com/lileio/slack"; 111 | package slack; 112 | 113 | message Request { 114 | string id = 1; 115 | } 116 | 117 | message Response { 118 | string id = 1; 119 | } 120 | 121 | service Slack { 122 | rpc Read (Request) returns (Response) {} 123 | } 124 | ``` 125 | 126 | 我们来修改一下使它能够提供真正的服务,并添加自己的方法。 127 | 128 | 我们来创建一个`Announce`方法向Slack发布消息。 129 | 130 | 我们假设Slack团队和身份验证已经由服务配置来处理,所以我们服务的用户只需要提供一个房间和他们的消息。 该服务将发送特殊的空响应,因为我们只需要知道是否发生错误,也不需要知道其他任何内容。 131 | 132 | 现在我们的`proto`文件看起来像这样: 133 | 134 | ``` protobuf 135 | syntax = "proto3"; 136 | option go_package = "github.com/lileio/slack"; 137 | import "google/protobuf/empty.proto"; 138 | package slack; 139 | 140 | message AnnounceRequest { 141 | string channel = 1; 142 | string msg = 2; 143 | } 144 | 145 | service Slack { 146 | rpc Announce (AnnounceRequest) returns (google.protobuf.Empty) {} 147 | } 148 | ``` 149 | 150 | 现在我们运行`protoc`工具我们的文件,以及Lile生成器插件。 151 | 152 | ``` 153 | protoc -I . slack.proto --lile-server_out=. --go_out=plugins=grpc:$GOPATH/src 154 | ``` 155 | 156 | Lile提供了一个`Makefile`,每个项目都有一个已经配置的`proto`构建步骤。 所以我们可以运行它。 157 | 158 | ``` 159 | make proto 160 | ``` 161 | 162 | 我们可以看到,Lile将在`server`目录中为我们创建两个文件。 163 | 164 | ``` 165 | $ make proto 166 | protoc -I . slack.proto --lile-server_out=. --go_out=plugins=grpc:$GOPATH/src 167 | 2017/07/12 15:44:01 [Creating] server/announce.go 168 | 2017/07/12 15:44:01 [Creating test] server/announce_test.go 169 | ``` 170 | 171 | 我们来看看Lile为我们创建的`announce.go`文件。 172 | 173 | ``` go 174 | package server 175 | 176 | import ( 177 | "errors" 178 | 179 | "github.com/golang/protobuf/ptypes/empty" 180 | "github.com/lileio/slack" 181 | context "golang.org/x/net/context" 182 | ) 183 | 184 | func (s SlackServer) Announce(ctx context.Context, r *slack.AnnounceRequest) (*empty.Empty, error) { 185 | return nil, errors.New("not yet implemented") 186 | } 187 | ``` 188 | 189 | 接下来我们实现这个生成的方法,让我们从测试开始吧! 190 | 191 | 192 | ## 编写并运行测试 193 | 194 | 当您使用Lile生成RPC方法时,也会创建一个对应的测试文件。例如,给定我们的`announce.go`文件,Lile将在同一目录中创建`announce_test.go` 195 | 196 | 看起来如下: 197 | 198 | ``` go 199 | package server 200 | 201 | import ( 202 | "testing" 203 | 204 | "github.com/lileio/slack" 205 | "github.com/stretchr/testify/assert" 206 | context "golang.org/x/net/context" 207 | ) 208 | 209 | func TestAnnounce(t *testing.T) { 210 | ctx := context.Background() 211 | req := &slack.AnnounceRequest{} 212 | 213 | res, err := cli.Announce(ctx, req) 214 | assert.Nil(t, err) 215 | assert.NotNil(t, res) 216 | } 217 | 218 | ``` 219 | 220 | 您现在可以使用`Makefile`运行测试,并运行`make test`命令 221 | 222 | ``` 223 | $ make test 224 | === RUN TestAnnounce 225 | --- FAIL: TestAnnounce (0.00s) 226 | Error Trace: announce_test.go:16 227 | Error: Expected nil, but got: &status.statusError{Code:2, Message:"not yet implemented", Details:[]*any.Any(nil)} 228 | Error Trace: announce_test.go:17 229 | Error: Expected value not to be nil. 230 | FAIL 231 | coverage: 100.0% of statements 232 | FAIL github.com/lileio/slack/server 0.011s 233 | make: *** [test] Error 2 234 | 235 | ``` 236 | 237 | 我们的测试失败了,因为我们还没有实现我们的方法,在我们的方法中返回一个“未实现”的错误。 238 | 239 | 让我们在`announce.go`中实现`Announce`方法,这里是一个使用`nlopes`的[slack library](https://github.com/nlopes/slack)的例子。 240 | 241 | ``` go 242 | package server 243 | 244 | import ( 245 | "google.golang.org/grpc/codes" 246 | "google.golang.org/grpc/status" 247 | 248 | "github.com/golang/protobuf/ptypes/empty" 249 | "github.com/lileio/slack" 250 | sl "github.com/nlopes/slack" 251 | context "golang.org/x/net/context" 252 | ) 253 | 254 | var api = sl.New(os.Getenv("SLACK_TOKEN")) 255 | 256 | func (s SlackServer) Announce(ctx context.Context, r *slack.AnnounceRequest) (*empty.Empty, error) { 257 | _, _, err := api.PostMessage(r.Channel, r.Msg, sl.PostMessageParameters{}) 258 | if err != nil { 259 | return nil, status.Errorf(codes.Internal, err.Error()) 260 | } 261 | 262 | return &empty.Empty{}, nil 263 | } 264 | ``` 265 | 266 | 我们再次修改我们的测试用力,然后再次运行我们的测试 267 | 268 | ``` go 269 | package server 270 | 271 | import ( 272 | "testing" 273 | 274 | "github.com/lileio/slack" 275 | "github.com/stretchr/testify/assert" 276 | context "golang.org/x/net/context" 277 | ) 278 | 279 | func TestAnnounce(t *testing.T) { 280 | ctx := context.Background() 281 | req := &slack.AnnounceRequest{ 282 | Channel: "@alex", 283 | Msg: "hellooo", 284 | } 285 | 286 | res, err := cli.Announce(ctx, req) 287 | assert.Nil(t, err) 288 | assert.NotNil(t, res) 289 | } 290 | ``` 291 | 现在如果我使用我的Slack令牌作为环境变量运行测试,我应该看到通过测试! 292 | 293 | ``` 294 | $ alex@slack: SLACK_TOKEN=zbxkkausdkasugdk make test 295 | go test -v ./... -cover 296 | ? github.com/lileio/slack [no test files] 297 | === RUN TestAnnounce 298 | --- PASS: TestAnnounce (0.32s) 299 | PASS 300 | coverage: 75.0% of statements 301 | ok github.com/lileio/slack/server 0.331s coverage: 75.0% of statements 302 | ? github.com/lileio/slack/slack [no test files] 303 | ? github.com/lileio/slack/slack/cmd [no test files] 304 | ? github.com/lileio/slack/subscribers [no test files] 305 | ``` 306 | 307 | ## 使用生成的命令行 308 | 309 | 生成您的服务时,Lile生成一个命令行应用程序。 您可以使用自己的命令行扩展应用程序或使用内置的命令行来运行服务。 310 | 311 | 运行没有任何参数的命令行应用程序将打印生成的帮助。 312 | 313 | 例如`go run orders/main.go` 314 | 315 | ### 服务 316 | 317 | 运行`serve`将运行RPC服务。 318 | 319 | ### 订阅 320 | 321 | 运行`subscribe`订阅者将会收到你的订阅发布事件。 322 | 323 | ### up 324 | 325 | 运行`up`将同时运行RPC服务器和发布订阅的订阅者。 326 | 327 | ## 自定义命令行 328 | 329 | 要添加您自己的命令行,您可以使用[cobra](https://github.com/spf13/cobra),它是Lile的内置的命令行生成器。 330 | 331 | ``` bash 332 | $ cd orders 333 | $ cobra add import 334 | ``` 335 | 336 | 您现在可以编辑生成的文件,以创建您的命令行,`cobra`会自动将命令行的名称添加到帮助中。 337 | 338 | ## 暴露Prometheus采集指标 339 | 340 | 默认情况下,Lile将[Prometheus](prometheus.io)的采集指标暴露在`:9000/metrics`。 341 | 342 | 如果您的服务正在运行,您可以使用cURL来预览Prometheus指标。 343 | 344 | ``` 345 | $ curl :9000/metrics 346 | ``` 347 | 348 | 你应该看到如下的一些输出: 349 | 350 | ``` 351 | # HELP go_gc_duration_seconds A summary of the GC invocation durations. 352 | # TYPE go_gc_duration_seconds summary 353 | go_gc_duration_seconds{quantile="0"} 0 354 | go_gc_duration_seconds{quantile="0.25"} 0 355 | go_gc_duration_seconds{quantile="0.5"} 0 356 | go_gc_duration_seconds{quantile="0.75"} 0 357 | go_gc_duration_seconds{quantile="1"} 0 358 | go_gc_duration_seconds_sum 0 359 | go_gc_duration_seconds_count 0 360 | ... 361 | ... 362 | ``` 363 | 364 | Lile的Prometheus指标实现使用go-grpc-promesheus的拦截器将其内置到自身的gPRC中,提供如以下的指标: 365 | 366 | ``` 367 | grpc_server_started_total 368 | grpc_server_msg_received_total 369 | grpc_server_msg_sent_total 370 | grpc_server_handling_seconds_bucket 371 | ``` 372 | 373 | 有关使用Prometheus的更多信息,收集和绘制这些指标,请参阅[Prometheus入门](https://prometheus.io/docs/introduction/getting_started/) 374 | 375 | 有关gRPC Prometheus查询的示例,请参阅[查询示例](https://github.com/grpc-ecosystem/go-grpc-prometheus#useful-query-examples)。 376 | 377 | ## 发布和订阅 378 | 379 | 虽然大多数服务将主要通过RPC进行通信,但Lile提供了一个[pubsub](https://github.com/lileio/lile/tree/master/pubsub)来执行[Publish & Subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)通信,这在开发异步的请求时特别有用。 380 | 381 | You can either manually publish events or use the gRPC [middleware](https://github.com/lileio/lile/blob/master/pubsub/interceptor.go) to automatically publish an event when a RPC method is called. 382 | 383 | 发布者与订阅者是松耦合的,甚至不知道它们的存在。大多数[Lile](https://github.com/lileio)已经内置了事件钩子,但是您也可以很容易的添加事件到您的服务中。您可以手动发布事件或使用gRPC[中间件](https://github.com/lileio/lile/blob/master/pubsub/interceptor.go)在调用RPC方法时自动发布事件。 384 | 385 | Lile的发布订阅是基于每个用户存在“至少一次”消息传递。换句话说,给定一个发布事件`account_created`的`account_service`(发布者),如果`email_service`(subscriber)和`fraud_detection_service`(subscriber)的多个实例正在运行,则每个`email_service`和`fraud_detection_service`都只有一个实例收到一条消息。 386 | 387 | ## 发布事件 388 | 389 | 如果配置了发布订阅(通过环境自动或手动进行),那么只需要一个简单的“Publish”调用。 390 | 391 | 以下是“订单”服务的“Get”方法示例: 392 | 393 | ``` go 394 | func (s OrdersServer) Get(ctx context.Context, r *orders.GetRequest) (*orders.GetResponse, error) { 395 | o, err := getOrder(r.Id) 396 | if err != nil { 397 | return nil, err 398 | } 399 | 400 | res := &orders.GetResponse{ 401 | Id: o.Id, 402 | Name: o.Name, 403 | } 404 | pubsub.Publish(ctx, "orders_service.Get", res) 405 | return res, nil 406 | } 407 | ``` 408 | 409 | `Publish`需要一个`context.Context`用于跟踪和指标,一个主题名称和一个`proto.Msg`,它是可以序列化到protobuf的任何对象。 410 | 411 | ## 自动发布事件 412 | 413 | 当使用了Lile的gPRC[中间件](https://github.com/lileio/lile/blob/master/pubsub/interceptor.go),在调用gRPC方法时,可以自动发布事件。 414 | 415 | 在我们的`main.go`中,我们可以添加发布订阅拦截器,将我们的gRPC方法映射到我们的发布订阅主题。 416 | 417 | 拦截器将自动发布gRPC响应到该主题。 418 | 419 | ``` go 420 | lile.AddPubSubInterceptor(map[string]string{ 421 | "Create": "account_service.created", 422 | "Update": "account_service.updated", 423 | }) 424 | ``` 425 | 426 | ## 订阅事件 427 | 428 | 默认情况下,Lile将在项目中生成一个具有一些订阅事件基本设置的`subscribers.go`文件。 429 | 430 | [`lile.Subscriber`](https://godoc.org/github.com/lileio/lile/pubsub#Subscriber)借口用于订阅符合`Setup`事件规范任何主题的事件。 431 | ```go 432 | type OrdersServiceSubscriber struct{} 433 | 434 | func (s *OrdersServiceSubscriber) Setup(c *pubsub.Client) { 435 | c.On("shipments.updated", s.ShipmentUpdate, 30*time.Second, true) 436 | } 437 | 438 | func (s *OrdersServiceSubscriber) ShipmentUpdate(sh *shipments.Shipment) { 439 | // do something with sh 440 | } 441 | ``` 442 | 443 | [Handler interface](https://godoc.org/github.com/lileio/lile/pubsub#Handler)是用于监听符合发布订阅的任何内容的功能。 444 | 445 | Protobuf消息自动解码。 446 | 447 | ## 追踪 448 | 449 | Lile已经建立了跟踪,将[opentracing](http://opentracing.io/) 兼容的跟踪器设置为`GlobalTracer`,默认情况下,Lile报告所有gRPC方法和发布/订阅操作。 450 | 451 | ![](https://2.bp.blogspot.com/-0pFWb8zb-Cg/WPb9qKoDwDI/AAAAAAAAD2g/VjUFl1-_tYgy6zpzw0iyjfwh3gh0rg92wCLcB/s640/go-2.png) 452 | 453 | ### Zipkin 454 | 455 | 要通过HTTP将所有跟踪事件发送到[Zipkin](http://zipkin.io),请将环境变量`ZIPKIN_SERVICE_HOST`设置为Zipkin服务的DNS名称。 如果服务已经运行命名为“zipkin”,Kubernetes将自动将`ZIPKIN_SERVICE_HOST`暴露给容器。 456 | 457 | ### Stackdriver (Google Cloud Platform) Trace 458 | 459 | 如果你想使用追踪而又不想维护Zipkin,Stackdriver提供了一个`zipkin-collector`映像,它将侦听Zipkin跟踪,转换并发送到Stackdriver。更多请参阅[Google Cloud Tracing](https://cloud.google.com/trace/docs/zipkin#option_1_using_a_container_image_to_set_up_your_server) -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= 3 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 4 | cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= 5 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 6 | git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= 7 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 8 | github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 9 | github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= 10 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 11 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 12 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 13 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 14 | github.com/apache/thrift v0.0.0-20181016064013-5c1ecb67cde4 h1:FBR7tMe5n9KFxfDYgGqAbhH3Zt5u5Sx036fax6OpPno= 15 | github.com/apache/thrift v0.0.0-20181016064013-5c1ecb67cde4/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 16 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= 17 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 18 | github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= 19 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 20 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= 21 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 22 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 23 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 24 | github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 25 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 27 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/dropbox/godropbox v0.0.0-20180512210157-31879d3884b9 h1:NAvZb7gqQfLSNBPzVsvI7eZMosXtg2g2kxXrei90CtU= 29 | github.com/dropbox/godropbox v0.0.0-20180512210157-31879d3884b9/go.mod h1:glr97hP/JuXb+WMYCizc4PIFuzw1lCR97mwbe1VVXhQ= 30 | github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= 31 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 32 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= 33 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 34 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= 35 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 36 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 37 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 38 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 39 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 40 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 41 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 42 | github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= 43 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 44 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 45 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 46 | github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= 47 | github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 48 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= 49 | github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 50 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 51 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 52 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 53 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 54 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 55 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 56 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 57 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 58 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 59 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 60 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 61 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 62 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 63 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 64 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= 65 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 66 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 67 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 68 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 69 | github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= 70 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 71 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 72 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 73 | github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= 74 | github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= 75 | github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= 76 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 77 | github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= 78 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 79 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 80 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 81 | github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= 82 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 83 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= 84 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 85 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 86 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 87 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= 88 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= 89 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 90 | github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 91 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 92 | github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 93 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 94 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 95 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 96 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 97 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 98 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 99 | github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= 100 | github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= 101 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 102 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 103 | github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7 h1:ux/56T2xqZO/3cP1I2F86qpeoYPCOzk+KF/UH/Ar+lk= 104 | github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= 105 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 106 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 107 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME= 108 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= 109 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 110 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 111 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 112 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 113 | github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 114 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 115 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= 116 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 117 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= 118 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 119 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 120 | github.com/lileio/fromenv v0.0.0-20180720125005-e881ea03b503/go.mod h1:VhsksrdjU8n+Lx6GW2u+6cbwD5wY7Z5/6N/HGlCH/mg= 121 | github.com/lileio/fromenv v1.4.0 h1:00zDYKl4USMFCOD4eLvGy8BbCvkQRzUlRVqkIxzog9Q= 122 | github.com/lileio/fromenv v1.4.0/go.mod h1:NDKhkr4fE98LZDpXJKP46GHXncBQF77MTr6Urjlv4J0= 123 | github.com/lileio/lile v0.0.0-20181016094446-48bc7aaffacf h1:M6EnuFFbKtlLRqf+KgQobAs/WZiDnALlkvjDEsQH41U= 124 | github.com/lileio/lile v0.0.0-20181016094446-48bc7aaffacf/go.mod h1:rNQbm4tF7jCeCdyCMrq9tABCXbrFzYdKmrCTlPcCyYY= 125 | github.com/lileio/logr v0.0.0-20180612103852-dc57c81cbed7/go.mod h1:/yovbnNwQYF/hEzq8tH5pXj7Rp0OUevoWWnaF4InxCE= 126 | github.com/lileio/logr v1.1.0 h1:LXFoQueH0I683/P/k1vmOHXnr0OWTuj3FwMEuRNxgs0= 127 | github.com/lileio/logr v1.1.0/go.mod h1:tuh1La46EvEN9PYHlCNvZsk473ULi0GDsEvcEe6nQr0= 128 | github.com/lileio/pubsub v0.0.0-20180730130251-70c350806efc h1:f1F6msX/GoQEUeulT+XnpPHhuOD47liCet33gU4xdww= 129 | github.com/lileio/pubsub v0.0.0-20180730130251-70c350806efc/go.mod h1:h/M3uzqD+bln5846G81pqq3CyvhCBhVGEjdOZkP4r3Y= 130 | github.com/lileio/pubsub/v2 v2.3.1 h1:MKfOX7fOC51JIw5SFdT2bVNr7/CNpotraQPHUINESmM= 131 | github.com/lileio/pubsub/v2 v2.3.1/go.mod h1:VO9m/1AGWfVNg2KrCD46Q/9C3DZYtsWRIOMg+avPGm0= 132 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= 133 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 134 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 135 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 136 | github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= 137 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 138 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 139 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 140 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 141 | github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce h1:TqjP/BTDrwN7zP9xyXVuLsMBXYMt6LLYi55PlrIcq8U= 142 | github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:ifHPsLndGGzvgzcaXUvzmt6LxKT4pJ+uzEhtnMt+f7A= 143 | github.com/nats-io/jwt v0.2.14/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= 144 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= 145 | github.com/nats-io/nats-server/v2 v2.0.4/go.mod h1:AWdGEVbjKRS9ZIx4DSP5eKW48nfFm7q3uiSkP/1KD7M= 146 | github.com/nats-io/nats-server/v2 v2.1.0/go.mod h1:r5y0WgCag0dTj/qiHkHrXAcKQ/f5GMOZaEGdoxxnJ4I= 147 | github.com/nats-io/nats-streaming-server v0.16.2/go.mod h1:P12vTqmBpT6Ufs+cu0W1C4N2wmISqa6G4xdLQeO2e2s= 148 | github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= 149 | github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= 150 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= 151 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 152 | github.com/nats-io/stan.go v0.5.0/go.mod h1:dYqB+vMN3C2F9pT1FRQpg9eHbjPj6mP0yYuyBNuXHZE= 153 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 154 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 155 | github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= 156 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 157 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 158 | github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= 159 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 160 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= 161 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= 162 | github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= 163 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 164 | github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= 165 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 166 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.3.4 h1:x/pBv/5VJNWkcHF1G9xqhug8Iw7X1y1zOMzDmyuvP2g= 167 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.3.4/go.mod h1:js2AbwmHW0YD9DwIw2JhQWmbfFi/UnWyYwdVhqbCDOE= 168 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3 h1:XudIMByQMXJ6oDHy4SipNyo35LxjA69Z7v1nL0aAZvA= 169 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= 170 | github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= 171 | github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 172 | github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= 173 | github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 174 | github.com/openzipkin/zipkin-go-opentracing v0.3.4/go.mod h1:js2AbwmHW0YD9DwIw2JhQWmbfFi/UnWyYwdVhqbCDOE= 175 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 176 | github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= 177 | github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= 178 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 179 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 180 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 181 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 182 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 183 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 184 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 185 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 186 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 187 | github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 188 | github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 189 | github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno= 190 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 191 | github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= 192 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 193 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 194 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= 195 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 196 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 197 | github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 198 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 199 | github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= 200 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 201 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 202 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 203 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 204 | github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= 205 | github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= 206 | github.com/rakyll/statik v0.1.7-0.20190731211841-925a23bda946 h1:orVX1a3FYCOkDzVLCW342uDPd0DA0g2D5yDvCl5izOo= 207 | github.com/rakyll/statik v0.1.7-0.20190731211841-925a23bda946/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= 208 | github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= 209 | github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 210 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 211 | github.com/sanity-io/litter v1.1.0 h1:BllcKWa3VbZmOZbDCoszYLk7zCsKHz5Beossi8SUcTc= 212 | github.com/sanity-io/litter v1.1.0/go.mod h1:CJ0VCw2q4qKU7LaQr3n7UOSHzgEMgcGco7N/SkZQPjw= 213 | github.com/sanity-io/litter v1.2.0 h1:DGJO0bxH/+C2EukzOSBmAlxmkhVMGqzvcx/rvySYw9M= 214 | github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= 215 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 216 | github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= 217 | github.com/segmentio/ksuid v1.0.2/go.mod h1:BXuJDr2byAiHuQaQtSKoXh1J0YmUDurywOXgB2w+OSU= 218 | github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 h1:ofR1ZdrNSkiWcMsRrubK9tb2/SlZVWttAfqUjJi6QYc= 219 | github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= 220 | github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg= 221 | github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= 222 | github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 223 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 224 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 225 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 226 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 227 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 228 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 229 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 230 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 231 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 232 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 233 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 234 | github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 235 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 236 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 237 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 238 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 239 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 240 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 241 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 242 | github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6 h1:YdYsPAZ2pC6Tow/nPZOPQ96O3hm/ToAkGsPLzedXERk= 243 | github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= 244 | github.com/xtgo/set v1.0.0 h1:6BCNBRv3ORNDQ7fyoJXRv+tstJz3m1JVFQErfeZz2pY= 245 | github.com/xtgo/set v1.0.0/go.mod h1:d3NHzGzSa0NmB2NhFyECA+QdRp29oEn2xbT+TpeFoM8= 246 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 247 | go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI= 248 | go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= 249 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 250 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 251 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 252 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 253 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 254 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 255 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 256 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 257 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 258 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 259 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 260 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 261 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 262 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 263 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 264 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 265 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 266 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 267 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 268 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 269 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 270 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 271 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 272 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 273 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 274 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 275 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 276 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 277 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ= 278 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 279 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 280 | golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 281 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= 282 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 283 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= 284 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 285 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 286 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 287 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 288 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 289 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= 290 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 291 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 292 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 293 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 294 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 295 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 296 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 297 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 298 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= 299 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 300 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20191018095205-727590c5006e h1:ZtoklVMHQy6BFRHkbG6JzK+S6rX82//Yeok1vMlizfQ= 308 | golang.org/x/sys v0.0.0-20191018095205-727590c5006e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 310 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 311 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 312 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 313 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 314 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 315 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 316 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 317 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 318 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 319 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 320 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 321 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 322 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 323 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 324 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 325 | google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 326 | google.golang.org/api v0.0.0-20181016000437-c51f30376ab7/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 327 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 328 | google.golang.org/api v0.5.0 h1:lj9SyhMzyoa38fgFF0oO2T6pjs5IzkLPKfVtxpyCRMM= 329 | google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 330 | google.golang.org/api v0.11.0 h1:n/qM3q0/rV2F0pox7o0CvNhlPvZAo7pLbef122cbLJ0= 331 | google.golang.org/api v0.11.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 332 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 333 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 334 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 335 | google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= 336 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 337 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 338 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 339 | google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f h1:FU37niK8AQ59mHcskRyQL7H0ErSeNh650vdcj8HqdSI= 340 | google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 341 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk= 342 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 343 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 344 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg= 345 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 346 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 347 | google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 348 | google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= 349 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 350 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= 351 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 352 | google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 353 | google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= 354 | google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 355 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 356 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 357 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 358 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 359 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 360 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= 361 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 362 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 363 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 364 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 365 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 366 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 367 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 368 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 369 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 370 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 371 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 372 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 373 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 374 | --------------------------------------------------------------------------------