[#25] Refactoring and make fixes

closes #25
closes #32

Signed-off-by: Evgeniy Kulikov <kim@nspcc.ru>
remotes/KirillovDenis/bugfix/681-fix_acl_parsing
Evgeniy Kulikov 2020-10-23 03:12:37 +03:00
parent fbd4a83602
commit 4d605d1113
11 changed files with 82 additions and 51 deletions

View File

@ -75,6 +75,10 @@ func (h *handler) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
return
}
if len(list) > 0 {
own = list[0].Owner
}
res = &ListBucketsResponse{
Owner: Owner{
ID: own.String(),

View File

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neofs-api-go/pkg/client"
"github.com/nspcc-dev/neofs-api-go/pkg/container"
"github.com/nspcc-dev/neofs-api-go/pkg/owner"
"github.com/nspcc-dev/neofs-s3-gate/api"
"github.com/nspcc-dev/neofs-s3-gate/auth"
"go.uber.org/zap"
@ -16,6 +17,7 @@ type (
BucketInfo struct {
Name string
CID *container.ID
Owner *owner.ID
Created time.Time
}
@ -69,11 +71,13 @@ func (n *layer) containerInfo(ctx context.Context, cid *container.ID) (*BucketIn
return nil, err
}
info.Owner = owner.NewIDFromV2(res.GetOwnerID())
for _, attr := range res.GetAttributes() {
switch key, val := attr.GetKey(), attr.GetValue(); key {
case ContainerName:
case container.AttributeName:
info.Name = val
case LocallyCreationTime:
case container.AttributeTimestamp:
unix, err := strconv.ParseInt(attr.GetValue(), 10, 64)
if err != nil {
n.log.Error("could not parse container creation time",
@ -92,7 +96,7 @@ func (n *layer) containerInfo(ctx context.Context, cid *container.ID) (*BucketIn
return info, nil
}
func (n *layer) containerList(ctx context.Context) ([]BucketInfo, error) {
func (n *layer) containerList(ctx context.Context) ([]*BucketInfo, error) {
rid := api.GetRequestID(ctx)
bearer, err := auth.GetBearerToken(ctx)
if err != nil {
@ -128,7 +132,7 @@ func (n *layer) containerList(ctx context.Context) ([]BucketInfo, error) {
return nil, err
}
list := make([]BucketInfo, 0, len(res))
list := make([]*BucketInfo, 0, len(res))
for _, cid := range res {
info, err := n.containerInfo(ctx, cid)
if err != nil {
@ -138,7 +142,7 @@ func (n *layer) containerList(ctx context.Context) ([]BucketInfo, error) {
continue
}
list = append(list, *info)
list = append(list, info)
}
return list, nil

View File

@ -69,7 +69,7 @@ type (
Client interface {
NeoFS
ListBuckets(ctx context.Context) ([]BucketInfo, error)
ListBuckets(ctx context.Context) ([]*BucketInfo, error)
GetBucketInfo(ctx context.Context, name string) (*BucketInfo, error)
GetObject(ctx context.Context, p *GetObjectParams) error
@ -129,7 +129,7 @@ func (n *layer) GetBucketInfo(ctx context.Context, name string) (*BucketInfo, er
for _, bkt := range list {
if bkt.Name == name {
return &bkt, nil
return bkt, nil
}
}
@ -138,7 +138,7 @@ func (n *layer) GetBucketInfo(ctx context.Context, name string) (*BucketInfo, er
// ListBuckets returns all user containers. Name of the bucket is a container
// id. Timestamp is omitted since it is not saved in neofs container.
func (n *layer) ListBuckets(ctx context.Context) ([]BucketInfo, error) {
func (n *layer) ListBuckets(ctx context.Context) ([]*BucketInfo, error) {
return n.containerList(ctx)
}
@ -240,18 +240,18 @@ func (n *layer) GetObject(ctx context.Context, p *GetObjectParams) error {
var (
err error
oid *object.ID
cid = container.NewID()
bkt *BucketInfo
)
if err = cid.Parse(p.Bucket); err != nil {
if bkt, err = n.GetBucketInfo(ctx, p.Bucket); err != nil {
return err
} else if oid, err = n.objectFindID(ctx, &findParams{cid: cid, val: p.Object}); err != nil {
} else if oid, err = n.objectFindID(ctx, &findParams{cid: bkt.CID, val: p.Object}); err != nil {
return err
}
addr := object.NewAddress()
addr.SetObjectID(oid)
addr.SetContainerID(cid)
addr.SetContainerID(bkt.CID)
_, err = n.objectGet(ctx, &getParams{
Writer: p.Writer,
@ -293,6 +293,7 @@ func (n *layer) GetObjectInfo(ctx context.Context, bucketName, filename string)
}
func GetOwnerID(tkn *token.BearerToken) (*owner.ID, error) {
switch pkg.SDKVersion().GetMajor() {
case 2:
id := tkn.ToV2().GetBody().GetOwnerID()

View File

@ -5,6 +5,7 @@ import (
"context"
"io"
"net/http"
"strconv"
"time"
"github.com/nspcc-dev/neofs-api-go/pkg/client"
@ -62,13 +63,13 @@ func (n *layer) objectSearch(ctx context.Context, p *findParams) ([]*object.ID,
}
filter := object.NewSearchFilters()
filter.AddNonLeafFilter()
filter.AddRootFilter()
sop := new(client.SearchObjectParams)
sop.WithContainerID(p.cid)
if p.val != "" {
filter.AddFilter(ObjectName, p.val, object.MatchStringEqual)
filter.AddFilter(object.AttributeFileName, p.val, object.MatchStringEqual)
}
sop.WithSearchFilters(filter)
@ -100,7 +101,6 @@ func (n *layer) objectHead(ctx context.Context, addr *object.Address) (*object.O
ohp := new(client.ObjectHeaderParams)
ohp.WithAddress(addr)
ohp.WithAllFields()
ohp.WithMainFields()
return cli.GetObjectHeader(ctx, ohp, client.WithSession(tkn))
}
@ -116,6 +116,7 @@ func (n *layer) objectGet(ctx context.Context, p *getParams) (*object.Object, er
writer := newWriter(p.Writer, p.offset, p.length)
gop := new(client.GetObjectParams)
gop.WithAddress(p.addr)
gop.WithPayloadWriter(writer)
return cli.GetObject(ctx, gop, client.WithSession(tkn))
@ -126,8 +127,8 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo,
var (
err error
own *owner.ID
bkt *BucketInfo
brt *token.BearerToken
cid = container.NewID()
)
if brt, err = auth.GetBearerToken(ctx); err != nil {
@ -138,7 +139,7 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo,
_ = own
if bkt, err := n.GetBucketInfo(ctx, p.Bucket); err != nil {
if bkt, err = n.GetBucketInfo(ctx, p.Bucket); err != nil {
return nil, err
} else if _, err = n.objectFindID(ctx, &findParams{cid: bkt.CID, val: p.Object}); err == nil {
return nil, &api.ObjectAlreadyExists{
@ -154,11 +155,17 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo,
attributes := make([]*object.Attribute, 0, len(p.Header)+1)
unix := strconv.FormatInt(time.Now().UTC().Unix(), 64)
filename := object.NewAttribute()
filename.SetKey(ObjectName)
filename.SetKey(object.AttributeFileName)
filename.SetValue(p.Object)
attributes = append(attributes, filename)
createdAt := object.NewAttribute()
createdAt.SetKey(object.AttributeTimestamp)
createdAt.SetValue(unix)
attributes = append(attributes, filename, createdAt)
for k, v := range p.Header {
ua := object.NewAttribute()
@ -172,8 +179,8 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo,
r := io.TeeReader(p.Reader, b)
raw := object.NewRaw()
raw.SetOwnerID(tkn.OwnerID()) // should be replaced with BearerToken.GetOwnerID()
raw.SetContainerID(cid)
raw.SetOwnerID(tkn.OwnerID()) // should be replaced with BearerToken.Issuer()
raw.SetContainerID(bkt.CID)
raw.SetAttributes(attributes...)
pop := new(client.PutObjectParams)
@ -181,7 +188,7 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo,
pop.WithObject(raw.Object())
if _, err = cli.PutObject(ctx, pop, client.WithSession(tkn)); err != nil {
return nil, err
return nil, errors.Wrapf(err, "owner_id = %s", tkn.OwnerID())
}
return &ObjectInfo{

View File

@ -1,14 +0,0 @@
package layer
const (
// TODO should be replaced with well-known types from SDK
// ObjectName human readable name of the root object.
ObjectName = "filename"
// ContainerName human readable name of the container (`bucket`).
ContainerName = "dirname"
// LocallyCreationTime human readable creation time of the object / container.
LocallyCreationTime = "created_at"
)

View File

@ -1,6 +1,7 @@
package layer
import (
"fmt"
"net/http"
"os"
"strings"
@ -62,7 +63,7 @@ func objectInfoFromMeta(meta *object.Object) *ObjectInfo {
aws3name := meta.GetID().String()
userHeaders := userHeaders(meta.GetAttributes())
if name, ok := userHeaders[ObjectName]; ok {
if name, ok := userHeaders[object.AttributeFileName]; ok {
aws3name = name
delete(userHeaders, name)
}
@ -82,8 +83,12 @@ func objectInfoFromMeta(meta *object.Object) *ObjectInfo {
func nameFromObject(o *object.Object) (string, string) {
var name = o.GetID().String()
fmt.Printf("OID: %s\n", name)
fmt.Println("Attributes:")
for _, attr := range o.GetAttributes() {
if attr.GetKey() == ObjectName {
fmt.Printf("\t%s = %s\n", attr.GetKey(), attr.GetValue())
if attr.GetKey() == object.AttributeFileName {
name = attr.GetValue()
break

View File

@ -238,7 +238,7 @@ func (p *pool) ReBalance(ctx context.Context) {
{ // try to prepare token
ctx, cancel := context.WithTimeout(ctx, p.reqTimeout)
tkn, err = prepareToken(ctx, conn, p.key)
tkn, err = p.prepareToken(ctx, conn)
cancel()
}
@ -256,7 +256,7 @@ func (p *pool) ReBalance(ctx context.Context) {
p.log.Debug("connected to node", zap.String("address", p.nodes[i].address))
} else if tkn, exists = p.tokens[conn.Target()]; exists {
// token exists, ignore
} else if tkn, err = prepareToken(ctx, conn, p.key); err != nil {
} else if tkn, err = p.prepareToken(ctx, conn); err != nil {
p.log.Error("could not prepare session token",
zap.String("address", p.nodes[i].address),
zap.Error(err))

View File

@ -2,9 +2,10 @@ package pool
import (
"context"
"crypto/ecdsa"
"math"
"go.uber.org/zap"
"github.com/nspcc-dev/neofs-api-go/pkg/client"
"github.com/nspcc-dev/neofs-api-go/pkg/token"
"google.golang.org/grpc"
@ -20,7 +21,7 @@ func (p *pool) Token(ctx context.Context, conn *grpc.ClientConn) (*token.Session
}
// prepare session token
tkn, err := prepareToken(ctx, conn, p.key)
tkn, err := p.prepareToken(ctx, conn)
if err != nil {
return nil, err
}
@ -32,11 +33,20 @@ func (p *pool) Token(ctx context.Context, conn *grpc.ClientConn) (*token.Session
}
// creates token using
func prepareToken(ctx context.Context, con *grpc.ClientConn, key *ecdsa.PrivateKey) (*token.SessionToken, error) {
cli, err := client.New(key, client.WithGRPCConnection(con))
func (p *pool) prepareToken(ctx context.Context, conn *grpc.ClientConn) (*token.SessionToken, error) {
cli, err := client.New(p.key, client.WithGRPCConnection(conn))
if err != nil {
return nil, err
}
return cli.CreateSession(ctx, math.MaxUint64)
tkn, err := cli.CreateSession(ctx, math.MaxUint64)
if err != nil {
return nil, err
}
p.log.Info("token created for connection",
zap.String("address", conn.Target()),
zap.Stringer("owner", tkn.OwnerID()))
return tkn, err
}

View File

@ -235,6 +235,7 @@ func (a *App) Server(ctx context.Context) {
// Use mux.Router as http.Handler
srv.Handler = router
srv.ErrorLog = zap.NewStdLog(a.log)
go func() {
a.log.Info("starting server",

5
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/google/uuid v1.1.2
github.com/gorilla/mux v1.7.4
github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20201020152448-c8f46f7d9762
github.com/nspcc-dev/neofs-authmate v0.0.0
github.com/nspcc-dev/neofs-crypto v0.3.0
@ -24,13 +25,13 @@ require (
go.uber.org/atomic v1.6.0
go.uber.org/zap v1.16.0
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
golang.org/x/sys v0.0.0-20200806125547-5acd03effb82 // indirect
golang.org/x/text v0.3.3 // indirect
golang.org/x/tools v0.0.0-20200123022218-593de606220b // indirect
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 // indirect
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 // indirect
google.golang.org/grpc v1.33.0
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/ini.v1 v1.57.0 // indirect
)

20
go.sum
View File

@ -334,6 +334,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nspcc-dev/dbft v0.0.0-20191205084618-dacb1a30c254/go.mod h1:w1Ln2aT+dBlPhLnuZhBV+DfPEdS2CHWWLp5JTScY3bw=
github.com/nspcc-dev/dbft v0.0.0-20191209120240-0d6b7568d9ae/go.mod h1:3FjXOoHmA51EGfb5GS/HOv7VdmngNRTssSeQ729dvGY=
github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a/go.mod h1:/YFK+XOxxg0Bfm6P92lY5eDSLYfp06XOdL8KAVgXjVk=
@ -438,6 +440,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@ -497,6 +500,7 @@ github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191128022950-c6266f4fe8d7/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
@ -562,6 +566,8 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -581,8 +587,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -593,6 +599,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -663,15 +670,18 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200123022218-593de606220b h1:ztSlcncMErSAUzXwnVO1iTPxHwtvOHBB26SGiyYXIEE=
golang.org/x/tools v0.0.0-20200123022218-593de606220b/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 h1:hzJjkvxUIF3bSt+v8N5tBQNx/605vszZJ+3XsIamzZo=
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@ -732,6 +742,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=