This commit is contained in:
Fedor Korotkiy 2020-03-12 01:46:45 +03:00
parent bb446505a8
commit 9d2e3cc0bb
13 changed files with 391 additions and 18 deletions

1
distbuild/disttest/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
workdir

View file

@ -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()
}
}

View file

@ -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
}

View file

@ -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'}])
}

View file

@ -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 {

View file

@ -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 задаёт уникальный идентификатор джоба.

View file

@ -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
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 {

View file

@ -1,4 +0,0 @@
package dist
type Build struct {
}

71
distbuild/pkg/dist/coordinator.go vendored Normal file
View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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
})

1
go.mod
View file

@ -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

22
go.sum
View file

@ -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=