From 9d2e3cc0bb2652ab3deada1d4ef7c364c912e6ca Mon Sep 17 00:00:00 2001 From: Fedor Korotkiy Date: Thu, 12 Mar 2020 01:46:45 +0300 Subject: [PATCH] WIP --- distbuild/disttest/.gitignore | 1 + distbuild/disttest/fixture.go | 127 +++++++++++++++++++++++++++ distbuild/disttest/recorder.go | 57 ++++++++++++ distbuild/disttest/simple_test.go | 31 +++++++ distbuild/pkg/artifact/cache.go | 2 +- distbuild/pkg/build/graph.go | 30 ++++++- distbuild/pkg/client/build.go | 16 ++-- distbuild/pkg/dist/build.go | 4 - distbuild/pkg/dist/coordinator.go | 71 +++++++++++++++ distbuild/pkg/filecache/filecache.go | 10 ++- distbuild/pkg/worker/worker.go | 37 ++++++-- go.mod | 1 + go.sum | 22 +++++ 13 files changed, 391 insertions(+), 18 deletions(-) create mode 100644 distbuild/disttest/.gitignore create mode 100644 distbuild/disttest/fixture.go create mode 100644 distbuild/disttest/recorder.go create mode 100644 distbuild/disttest/simple_test.go delete mode 100644 distbuild/pkg/dist/build.go create mode 100644 distbuild/pkg/dist/coordinator.go diff --git a/distbuild/disttest/.gitignore b/distbuild/disttest/.gitignore new file mode 100644 index 0000000..6aa2721 --- /dev/null +++ b/distbuild/disttest/.gitignore @@ -0,0 +1 @@ +workdir diff --git a/distbuild/disttest/fixture.go b/distbuild/disttest/fixture.go new file mode 100644 index 0000000..beba2bb --- /dev/null +++ b/distbuild/disttest/fixture.go @@ -0,0 +1,127 @@ +package disttest + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/slon/shad-go/distbuild/pkg/artifact" + "gitlab.com/slon/shad-go/distbuild/pkg/client" + "gitlab.com/slon/shad-go/distbuild/pkg/dist" + "gitlab.com/slon/shad-go/distbuild/pkg/filecache" + "gitlab.com/slon/shad-go/distbuild/pkg/worker" + "gitlab.com/slon/shad-go/tools/testtool" + + "go.uber.org/zap" +) + +type env struct { + RootDir string + Logger *zap.Logger + + Ctx context.Context + + Client *client.Client + Coordinator *dist.Coordinator + Workers []*worker.Worker + + HTTP *http.Server +} + +const nWorkers = 4 + +func newEnv(t *testing.T) (e *env, cancel func()) { + cwd, err := os.Getwd() + require.NoError(t, err) + + absCWD, err := filepath.Abs(cwd) + require.NoError(t, err) + + env := &env{ + RootDir: filepath.Join(absCWD, "workdir", t.Name()), + } + + require.NoError(t, os.RemoveAll(env.RootDir)) + require.NoError(t, os.MkdirAll(env.RootDir, 0777)) + + cfg := zap.NewDevelopmentConfig() + cfg.OutputPaths = []string{filepath.Join(env.RootDir, "test.log")} + env.Logger, err = cfg.Build() + require.NoError(t, err) + + t.Helper() + t.Logf("test is running inside %s; see test.log file for more info", filepath.Join("workdir", t.Name())) + + port, err := testtool.GetFreePort() + require.NoError(t, err) + addr := "127.0.0.1:" + port + coordinatorEndpoint := "http://" + addr + "/coordinator" + + var cancelRootContext func() + env.Ctx, cancelRootContext = context.WithCancel(context.Background()) + + env.Client = &client.Client{ + CoordinatorEndpoint: coordinatorEndpoint, + SourceDir: filepath.Join(absCWD, "testdata/src"), + Log: env.Logger.Named("client"), + } + + coordinatorCache, err := filecache.New(filepath.Join(env.RootDir, "coordinator", "filecache")) + require.NoError(t, err) + + env.Coordinator = dist.NewCoordinator( + env.Logger.Named("coordinator"), + coordinatorCache, + ) + + for i := 0; i < nWorkers; i++ { + workerName := fmt.Sprintf("worker%d", i) + workerDir := filepath.Join(env.RootDir, workerName) + + fileCache, err := filecache.New(filepath.Join(workerDir, "filecache")) + require.NoError(t, err) + + artifacts, err := artifact.NewCache(filepath.Join(workerDir, "artifacts")) + require.NoError(t, err) + + w := worker.New( + coordinatorEndpoint, + env.Logger.Named(workerName), + fileCache, + artifacts, + ) + + env.Workers = append(env.Workers, w) + } + + mux := http.NewServeMux() + mux.Handle("/coordinator/", http.StripPrefix("/coordinator", env.Coordinator)) + + for i, w := range env.Workers { + workerPrefix := fmt.Sprintf("/worker/%d", i) + mux.Handle(workerPrefix+"/", http.StripPrefix(workerPrefix, w)) + } + + env.HTTP = &http.Server{ + Addr: addr, + Handler: mux, + } + + lsn, err := net.Listen("tcp", env.HTTP.Addr) + require.NoError(t, err) + + go func() { + env.Logger.Error("http server stopped", zap.Error(env.HTTP.Serve(lsn))) + }() + + return env, func() { + cancelRootContext() + _ = env.HTTP.Shutdown(context.Background()) + _ = env.Logger.Sync() + } +} diff --git a/distbuild/disttest/recorder.go b/distbuild/disttest/recorder.go new file mode 100644 index 0000000..3bef98d --- /dev/null +++ b/distbuild/disttest/recorder.go @@ -0,0 +1,57 @@ +package disttest + +import ( + "gitlab.com/slon/shad-go/distbuild/pkg/build" +) + +type JobResult struct { + Stdout string + Stderr string + + Code *int + Error string +} + +type Recorder struct { + Jobs map[build.ID]*JobResult +} + +func NewRecorder() *Recorder { + return &Recorder{ + Jobs: map[build.ID]*JobResult{}, + } +} + +func (r *Recorder) job(jobID build.ID) *JobResult { + j, ok := r.Jobs[jobID] + if !ok { + j = &JobResult{} + r.Jobs[jobID] = j + } + return j +} + +func (r *Recorder) OnJobStdout(jobID build.ID, stdout []byte) error { + j := r.job(jobID) + j.Stdout += string(stdout) + return nil +} + +func (r *Recorder) OnJobStderr(jobID build.ID, stderr []byte) error { + j := r.job(jobID) + j.Stderr += string(stderr) + return nil +} + +func (r *Recorder) OnJobFinished(jobID build.ID) error { + j := r.job(jobID) + j.Code = new(int) + return nil +} + +func (r *Recorder) OnJobFailed(jobID build.ID, code int, error string) error { + j := r.job(jobID) + j.Code = &code + j.Error = error + return nil +} diff --git a/distbuild/disttest/simple_test.go b/distbuild/disttest/simple_test.go new file mode 100644 index 0000000..397f6d7 --- /dev/null +++ b/distbuild/disttest/simple_test.go @@ -0,0 +1,31 @@ +package disttest + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/slon/shad-go/distbuild/pkg/build" +) + +var echoGraph = build.Graph{ + Jobs: []build.Job{ + { + ID: build.ID{'a'}, + Name: "echo", + Cmds: []build.Cmd{ + {Exec: []string{"echo", "-n", "OK"}}, + }, + }, + }, +} + +func TestSingleCommand(t *testing.T) { + env, cancel := newEnv(t) + defer cancel() + + var recorder Recorder + require.NoError(t, env.Client.Build(env.Ctx, echoGraph, &recorder)) + + require.Equal(t, &JobResult{Stdout: "OK", Code: new(int)}, recorder.Jobs[build.ID{'a'}]) +} diff --git a/distbuild/pkg/artifact/cache.go b/distbuild/pkg/artifact/cache.go index ee75f9e..153e2fd 100644 --- a/distbuild/pkg/artifact/cache.go +++ b/distbuild/pkg/artifact/cache.go @@ -16,7 +16,7 @@ var ( type Cache struct{} func NewCache(root string) (*Cache, error) { - panic("implement me") + return &Cache{}, nil } func (c *Cache) Range(artifactFn func(file build.ID) error) error { diff --git a/distbuild/pkg/build/graph.go b/distbuild/pkg/build/graph.go index 8746b92..66c9c48 100644 --- a/distbuild/pkg/build/graph.go +++ b/distbuild/pkg/build/graph.go @@ -1,9 +1,37 @@ package build -import "crypto/sha1" +import ( + "crypto/sha1" + "encoding" + "encoding/hex" + "fmt" +) type ID [sha1.Size]byte +var ( + _ = encoding.TextMarshaler(ID{}) + _ = encoding.TextUnmarshaler(&ID{}) +) + +func (id ID) MarshalText() ([]byte, error) { + return []byte(hex.EncodeToString(id[:])), nil +} + +func (id *ID) UnmarshalText(b []byte) error { + raw, err := hex.DecodeString(string(b)) + if err != nil { + return err + } + + if len(raw) != len(id) { + return fmt.Errorf("invalid id size: %q", b) + } + + copy(id[:], raw) + return nil +} + // Job описывает одну вершину графа сборки. type Job struct { // ID задаёт уникальный идентификатор джоба. diff --git a/distbuild/pkg/client/build.go b/distbuild/pkg/client/build.go index 5813dfc..3dc9101 100644 --- a/distbuild/pkg/client/build.go +++ b/distbuild/pkg/client/build.go @@ -8,26 +8,28 @@ import ( "io/ioutil" "net/http" + "go.uber.org/zap" + "gitlab.com/slon/shad-go/distbuild/pkg/build" "gitlab.com/slon/shad-go/distbuild/pkg/proto" ) type Client struct { CoordinatorEndpoint string - - SourceDir string + SourceDir string + Log *zap.Logger } type BuildListener interface { OnJobStdout(jobID build.ID, stdout []byte) error - OnJobStderr(jobID build.ID, stdout []byte) error + OnJobStderr(jobID build.ID, stderr []byte) error OnJobFinished(jobID build.ID) error OnJobFailed(jobID build.ID, code int, error string) error } func (c *Client) uploadSources(ctx context.Context, src proto.MissingSources) error { - + return nil } func (c *Client) Build(ctx context.Context, graph build.Graph, lsn BuildListener) error { @@ -43,6 +45,8 @@ func (c *Client) Build(ctx context.Context, graph build.Graph, lsn BuildListener req.Header.Add("Content-Type", "application/json") req = req.WithContext(ctx) + c.Log.Debug("sending build request", zap.String("url", req.URL.String())) + rsp, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("build failed: %w", err) @@ -58,7 +62,7 @@ func (c *Client) Build(ctx context.Context, graph build.Graph, lsn BuildListener var missing proto.MissingSources if err := d.Decode(&missing); err != nil { - return err + return fmt.Errorf("error receiving source list: %w", err) } if err := c.uploadSources(ctx, missing); err != nil { @@ -68,7 +72,7 @@ func (c *Client) Build(ctx context.Context, graph build.Graph, lsn BuildListener for { var update proto.StatusUpdate if err := d.Decode(&update); err != nil { - return err + return fmt.Errorf("error receiving status update: %w", err) } switch { diff --git a/distbuild/pkg/dist/build.go b/distbuild/pkg/dist/build.go deleted file mode 100644 index 2ccacc8..0000000 --- a/distbuild/pkg/dist/build.go +++ /dev/null @@ -1,4 +0,0 @@ -package dist - -type Build struct { -} diff --git a/distbuild/pkg/dist/coordinator.go b/distbuild/pkg/dist/coordinator.go new file mode 100644 index 0000000..43a4bf3 --- /dev/null +++ b/distbuild/pkg/dist/coordinator.go @@ -0,0 +1,71 @@ +package dist + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "go.uber.org/zap" + + "gitlab.com/slon/shad-go/distbuild/pkg/build" + "gitlab.com/slon/shad-go/distbuild/pkg/filecache" + "gitlab.com/slon/shad-go/distbuild/pkg/proto" +) + +type Build struct { +} + +type Coordinator struct { + log *zap.Logger + mux *http.ServeMux + fileCache *filecache.Cache +} + +func NewCoordinator( + log *zap.Logger, + fileCache *filecache.Cache, +) *Coordinator { + c := &Coordinator{ + log: log, + mux: http.NewServeMux(), + fileCache: fileCache, + } + + c.mux.HandleFunc("/build", c.Build) + return c +} + +func (c *Coordinator) ServeHTTP(w http.ResponseWriter, r *http.Request) { + c.mux.ServeHTTP(w, r) +} + +func (c *Coordinator) doBuild(w http.ResponseWriter, r *http.Request) error { + graphJS, err := ioutil.ReadAll(r.Body) + if err != nil { + return err + } + + var g build.Graph + if err := json.Unmarshal(graphJS, &g); err != nil { + return err + } + + w.WriteHeader(http.StatusOK) + enc := json.NewEncoder(w) + if err := enc.Encode(proto.MissingSources{}); err != nil { + return err + } + + return fmt.Errorf("coordinator not implemented") +} + +func (c *Coordinator) Build(w http.ResponseWriter, r *http.Request) { + if err := c.doBuild(w, r); err != nil { + c.log.Error("build failed", zap.Error(err)) + + errorUpdate := proto.StatusUpdate{BuildFailed: &proto.BuildFailed{Error: err.Error()}} + errorJS, _ := json.Marshal(errorUpdate) + _, _ = w.Write(errorJS) + } +} diff --git a/distbuild/pkg/filecache/filecache.go b/distbuild/pkg/filecache/filecache.go index 96fec8a..868082f 100644 --- a/distbuild/pkg/filecache/filecache.go +++ b/distbuild/pkg/filecache/filecache.go @@ -2,8 +2,10 @@ package filecache import ( "errors" + "fmt" "io" "net/http" + "os" "gitlab.com/slon/shad-go/distbuild/pkg/build" ) @@ -15,10 +17,16 @@ var ( ) type Cache struct { + rootDir string } func New(rootDir string) (*Cache, error) { - panic("implement me") + if err := os.MkdirAll(rootDir, 0777); err != nil { + return nil, fmt.Errorf("error creating filecache: %w", err) + } + + c := &Cache{rootDir: rootDir} + return c, nil } func (c *Cache) Range(fileFn func(file build.ID) error) error { diff --git a/distbuild/pkg/worker/worker.go b/distbuild/pkg/worker/worker.go index 0cb3107..d0fa0d0 100644 --- a/distbuild/pkg/worker/worker.go +++ b/distbuild/pkg/worker/worker.go @@ -2,26 +2,53 @@ package worker import ( "context" + "net/http" "sync" + "go.uber.org/zap" + "gitlab.com/slon/shad-go/distbuild/pkg/artifact" "gitlab.com/slon/shad-go/distbuild/pkg/build" "gitlab.com/slon/shad-go/distbuild/pkg/filecache" ) type Worker struct { - CoordinatorEndpoint string + coordinatorEndpoint string - SourceFiles *filecache.Cache - Artifacts *artifact.Cache + log *zap.Logger + + fileCache *filecache.Cache + artifacts *artifact.Cache + + mux *http.ServeMux mu sync.Mutex newArtifacts []build.ID newSources []build.ID } +func New( + coordinatorEndpoint string, + log *zap.Logger, + fileCache *filecache.Cache, + artifacts *artifact.Cache, +) *Worker { + return &Worker{ + coordinatorEndpoint: coordinatorEndpoint, + log: log, + fileCache: fileCache, + artifacts: artifacts, + + mux: http.NewServeMux(), + } +} + +func (w *Worker) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + w.mux.ServeHTTP(rw, r) +} + func (w *Worker) recover() error { - err := w.SourceFiles.Range(func(file build.ID) error { + err := w.fileCache.Range(func(file build.ID) error { w.newSources = append(w.newSources, file) return nil }) @@ -29,7 +56,7 @@ func (w *Worker) recover() error { return err } - return w.Artifacts.Range(func(file build.ID) error { + return w.artifacts.Range(func(file build.ID) error { w.newArtifacts = append(w.newArtifacts, file) return nil }) diff --git a/go.mod b/go.mod index a495ba3..d28eb04 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/spf13/cobra v0.0.5 github.com/stretchr/testify v1.4.0 go.uber.org/goleak v1.0.0 + go.uber.org/zap v1.14.0 golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/perf v0.0.0-20191209155426-36b577b0eb03 golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825 diff --git a/go.sum b/go.sum index f68d302..21e1d52 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ cloud.google.com/go v0.0.0-20170206221025-ce650573d812/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes= @@ -25,6 +26,7 @@ github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+ github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= @@ -33,6 +35,7 @@ github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -43,8 +46,10 @@ github.com/mattn/go-sqlite3 v0.0.0-20161215041557-2d44decb4941/go.mod h1:FPy6Kqz github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -57,17 +62,28 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.14.0 h1:/pduUoebOeeJzTDFuoMgC6nRkiasr1sBCIEorly7m4o= +go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -89,6 +105,9 @@ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fq golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825 h1:aNQeSIHKi0RWpKA5NO0CqyLjx6Beh5l0LLUEnndEjz0= golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -102,9 +121,12 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=