client interface, test client errs

This commit is contained in:
Mark Chmarny 2020-06-22 07:54:32 -07:00
parent 5c2c2d4618
commit c9110e404a
15 changed files with 122 additions and 77 deletions

View File

@ -10,7 +10,7 @@ mod: ## Updates the go modules
test: mod ## Tests the entire project
go test -v -count=1 -race ./...
# go test -v -count=1 -run TestInvokeServiceWithContent ./...
# go test -v -count=1 -run NameOfSingleTest ./...
service: mod ## Runs the uncompiled example service code
dapr run --app-id serving \

View File

@ -9,7 +9,7 @@ import (
// InvokeBinding invokes specific operation on the configured Dapr binding.
// This method covers input, output, and bi-directional bindings.
func (c *Client) InvokeBinding(ctx context.Context, name, op string, in []byte, min map[string]string) (out []byte, mout map[string]string, err error) {
func (c *GRPCClient) InvokeBinding(ctx context.Context, name, op string, in []byte, min map[string]string) (out []byte, mout map[string]string, err error) {
if name == "" {
return nil, nil, errors.New("nil topic")
}
@ -35,7 +35,7 @@ func (c *Client) InvokeBinding(ctx context.Context, name, op string, in []byte,
// InvokeOutputBinding invokes configured Dapr binding with data (allows nil).InvokeOutputBinding
// This method differs from InvokeBinding in that it doesn't expect any content being returned from the invoked method.
func (c *Client) InvokeOutputBinding(ctx context.Context, name, operation string, data []byte) error {
func (c *GRPCClient) InvokeOutputBinding(ctx context.Context, name, operation string, data []byte) error {
_, _, err := c.InvokeBinding(ctx, name, operation, data, nil)
if err != nil {
return errors.Wrap(err, "error invoking output binding")

View File

@ -9,7 +9,7 @@ import (
func TestInvokeBinding(t *testing.T) {
ctx := context.Background()
client, closer := getTestClient(ctx)
client, closer := getTestClient(ctx, t)
defer closer()
mIn := make(map[string]string, 0)
@ -23,7 +23,7 @@ func TestInvokeBinding(t *testing.T) {
func TestInvokeOutputBinding(t *testing.T) {
ctx := context.Background()
client, closer := getTestClient(ctx)
client, closer := getTestClient(ctx, t)
defer closer()
err := client.InvokeOutputBinding(ctx, "serving", "EchoMethod", []byte("ping"))

View File

@ -19,11 +19,59 @@ const (
)
var (
logger = log.New(os.Stdout, "", 0)
logger = log.New(os.Stdout, "", 0)
_ Client = (*GRPCClient)(nil)
)
// Client is the interface for Dapr client implementation.
type Client interface {
// InvokeBinding invokes specific operation on the configured Dapr binding.
// This method covers input, output, and bi-directional bindings.
InvokeBinding(ctx context.Context, name, op string, in []byte, min map[string]string) (out []byte, mout map[string]string, err error)
// InvokeOutputBinding invokes configured Dapr binding with data (allows nil).InvokeOutputBinding
// This method differs from InvokeBinding in that it doesn't expect any content being returned from the invoked method.
InvokeOutputBinding(ctx context.Context, name, operation string, data []byte) error
// InvokeService invokes service without raw data ([]byte).
InvokeService(ctx context.Context, serviceID, method string) (out []byte, err error)
// InvokeServiceWithContent invokes service without content (data + content type).
InvokeServiceWithContent(ctx context.Context, serviceID, method, contentType string, data []byte) (out []byte, err error)
// PublishEvent pubishes data onto specific pubsub topic.
PublishEvent(ctx context.Context, topic string, in []byte) error
// GetSecret retreaves preconfigred secret from specified store using key.
GetSecret(ctx context.Context, store, key string, meta map[string]string) (out map[string]string, err error)
// SaveState saves the fully loaded state to store.
SaveState(ctx context.Context, s *State) error
// SaveStateData saves the raw data into store using default state options.
SaveStateData(ctx context.Context, store, key, etag string, data []byte) error
// SaveStateItem saves the single state item to store.
SaveStateItem(ctx context.Context, store string, item *StateItem) error
// GetState retreaves state from specific store using default consistency option.
GetState(ctx context.Context, store, key string) (out []byte, etag string, err error)
// GetStateWithConsistency retreaves state from specific store using provided state consistency.
GetStateWithConsistency(ctx context.Context, store, key string, sc StateConsistency) (out []byte, etag string, err error)
// DeleteState deletes content from store using default state options.
DeleteState(ctx context.Context, store, key string) error
// DeleteStateVersion deletes content from store using provided state options and etag.
DeleteStateVersion(ctx context.Context, store, key, etag string, opts *StateOptions) error
// Close cleans up all resources created by the client.
Close()
}
// NewClient instantiates Dapr client using DAPR_GRPC_PORT environment variable as port.
func NewClient() (client *Client, err error) {
func NewClient() (client Client, err error) {
port := os.Getenv(daprPortEnvVarName)
if port == "" {
port = daprPortDefault
@ -32,7 +80,7 @@ func NewClient() (client *Client, err error) {
}
// NewClientWithPort instantiates Dapr using specific port.
func NewClientWithPort(port string) (client *Client, err error) {
func NewClientWithPort(port string) (client Client, err error) {
if port == "" {
return nil, errors.New("nil port")
}
@ -40,7 +88,7 @@ func NewClientWithPort(port string) (client *Client, err error) {
}
// NewClientWithAddress instantiates Dapr using specific address (inclding port).
func NewClientWithAddress(address string) (client *Client, err error) {
func NewClientWithAddress(address string) (client Client, err error) {
if address == "" {
return nil, errors.New("nil address")
}
@ -53,21 +101,21 @@ func NewClientWithAddress(address string) (client *Client, err error) {
}
// NewClientWithConnection instantiates Dapr client using specific connection.
func NewClientWithConnection(conn *grpc.ClientConn) *Client {
return &Client{
func NewClientWithConnection(conn *grpc.ClientConn) Client {
return &GRPCClient{
connection: conn,
protoClient: pb.NewDaprClient(conn),
}
}
// Client is the Dapr client.
type Client struct {
// GRPCClient is the gRPC implementation of Dapr client.
type GRPCClient struct {
connection *grpc.ClientConn
protoClient pb.DaprClient
}
// Close cleans up all resources created by the client.
func (c *Client) Close() {
func (c *GRPCClient) Close() {
if c.connection != nil {
c.connection.Close()
}

View File

@ -4,7 +4,6 @@ import (
"context"
"net"
"testing"
"time"
"github.com/golang/protobuf/ptypes/empty"
"github.com/stretchr/testify/assert"
@ -16,9 +15,13 @@ import (
pb "github.com/dapr/go-sdk/dapr/proto/runtime/v1"
)
const (
testBufSize = 1024 * 1024
)
func TestNewClientWithConnection(t *testing.T) {
ctx := context.Background()
client, closer := getTestClient(ctx)
client, closer := getTestClient(ctx, t)
assert.NotNil(t, closer)
defer closer()
assert.NotNil(t, client)
@ -31,8 +34,8 @@ func TestNewClientWithoutArgs(t *testing.T) {
assert.NotNil(t, err)
}
func getTestClient(ctx context.Context) (client *Client, closer func()) {
l := bufconn.Listen(1024 * 1024)
func getTestClient(ctx context.Context, t *testing.T) (client Client, closer func()) {
l := bufconn.Listen(testBufSize)
s := grpc.NewServer()
server := &testDaprServer{
@ -43,18 +46,18 @@ func getTestClient(ctx context.Context) (client *Client, closer func()) {
go func() {
if err := s.Serve(l); err != nil {
logger.Fatalf("error starting test server: %s", err)
t.Fatalf("test server exited with error: %v", err)
}
}()
// wait for the server to start
time.Sleep(100 * time.Millisecond)
d := grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return l.Dial()
})
c, _ := grpc.DialContext(ctx, "", d, grpc.WithInsecure())
c, err := grpc.DialContext(ctx, "bufnet", d, grpc.WithInsecure())
if err != nil {
t.Fatalf("failed to dial bufnet: %v", err)
}
closer = func() {
l.Close()

View File

@ -9,7 +9,7 @@ import (
"github.com/pkg/errors"
)
func (c *Client) invokeServiceWithRequest(ctx context.Context, req *pb.InvokeServiceRequest) (out []byte, err error) {
func (c *GRPCClient) invokeServiceWithRequest(ctx context.Context, req *pb.InvokeServiceRequest) (out []byte, err error) {
if req == nil {
return nil, errors.New("nil request")
}
@ -30,7 +30,7 @@ func (c *Client) invokeServiceWithRequest(ctx context.Context, req *pb.InvokeSer
}
// InvokeService invokes service without raw data ([]byte).
func (c *Client) InvokeService(ctx context.Context, serviceID, method string) (out []byte, err error) {
func (c *GRPCClient) InvokeService(ctx context.Context, serviceID, method string) (out []byte, err error) {
if serviceID == "" {
return nil, errors.New("nil serviceID")
}
@ -47,7 +47,7 @@ func (c *Client) InvokeService(ctx context.Context, serviceID, method string) (o
}
// InvokeServiceWithContent invokes service without content (data + content type).
func (c *Client) InvokeServiceWithContent(ctx context.Context, serviceID, method, contentType string, data []byte) (out []byte, err error) {
func (c *GRPCClient) InvokeServiceWithContent(ctx context.Context, serviceID, method, contentType string, data []byte) (out []byte, err error) {
if serviceID == "" {
return nil, errors.New("nil serviceID")
}

View File

@ -9,7 +9,7 @@ import (
func TestInvokeServiceWithContent(t *testing.T) {
ctx := context.Background()
client, closer := getTestClient(ctx)
client, closer := getTestClient(ctx, t)
defer closer()
resp, err := client.InvokeServiceWithContent(ctx, "serving", "EchoMethod",
@ -21,7 +21,7 @@ func TestInvokeServiceWithContent(t *testing.T) {
func TestInvokeService(t *testing.T) {
ctx := context.Background()
client, closer := getTestClient(ctx)
client, closer := getTestClient(ctx, t)
defer closer()
resp, err := client.InvokeService(ctx, "serving", "EchoMethod")

View File

@ -8,7 +8,7 @@ import (
)
// PublishEvent pubishes data onto specific pubsub topic.
func (c *Client) PublishEvent(ctx context.Context, topic string, in []byte) error {
func (c *GRPCClient) PublishEvent(ctx context.Context, topic string, in []byte) error {
if topic == "" {
return errors.New("nil topic")
}

View File

@ -9,7 +9,7 @@ import (
func TestPublishEvent(t *testing.T) {
ctx := context.Background()
client, closer := getTestClient(ctx)
client, closer := getTestClient(ctx, t)
defer closer()
err := client.PublishEvent(ctx, "serving", []byte("ping"))

View File

@ -8,7 +8,7 @@ import (
)
// GetSecret retreaves preconfigred secret from specified store using key.
func (c *Client) GetSecret(ctx context.Context, store, key string, meta map[string]string) (out map[string]string, err error) {
func (c *GRPCClient) GetSecret(ctx context.Context, store, key string, meta map[string]string) (out map[string]string, err error) {
if store == "" {
return nil, errors.New("nil store")
}

View File

@ -9,7 +9,7 @@ import (
func GetSecret(t *testing.T) {
ctx := context.Background()
client, closer := getTestClient(ctx)
client, closer := getTestClient(ctx, t)
defer closer()
out, err := client.GetSecret(ctx, "store", "key1", nil)

View File

@ -175,7 +175,7 @@ func toProtoDuration(d time.Duration) *duration.Duration {
}
// SaveState saves the fully loaded state to store.
func (c *Client) SaveState(ctx context.Context, s *State) error {
func (c *GRPCClient) SaveState(ctx context.Context, s *State) error {
if s == nil || s.StoreName == "" || s.States == nil || len(s.States) < 1 {
return errors.New("nil or invalid state")
}
@ -187,25 +187,8 @@ func (c *Client) SaveState(ctx context.Context, s *State) error {
return nil
}
// SaveStateItem saves the single state item to store.
func (c *Client) SaveStateItem(ctx context.Context, store string, item *StateItem) error {
if store == "" {
return errors.New("nil store")
}
if item == nil {
return errors.New("nil item")
}
req := &State{
StoreName: store,
States: []*StateItem{item},
}
return c.SaveState(ctx, req)
}
// SaveStateData saves the raw data into store using default state options.
func (c *Client) SaveStateData(ctx context.Context, store, key, etag string, data []byte) error {
func (c *GRPCClient) SaveStateData(ctx context.Context, store, key, etag string, data []byte) error {
if store == "" {
return errors.New("nil store")
}
@ -227,8 +210,30 @@ func (c *Client) SaveStateData(ctx context.Context, store, key, etag string, dat
return c.SaveState(ctx, req)
}
// SaveStateItem saves the single state item to store.
func (c *GRPCClient) SaveStateItem(ctx context.Context, store string, item *StateItem) error {
if store == "" {
return errors.New("nil store")
}
if item == nil {
return errors.New("nil item")
}
req := &State{
StoreName: store,
States: []*StateItem{item},
}
return c.SaveState(ctx, req)
}
// GetState retreaves state from specific store using default consistency option.
func (c *GRPCClient) GetState(ctx context.Context, store, key string) (out []byte, etag string, err error) {
return c.GetStateWithConsistency(ctx, store, key, StateConsistencyStrong)
}
// GetStateWithConsistency retreaves state from specific store using provided state consistency.
func (c *Client) GetStateWithConsistency(ctx context.Context, store, key string, sc StateConsistency) (out []byte, etag string, err error) {
func (c *GRPCClient) GetStateWithConsistency(ctx context.Context, store, key string, sc StateConsistency) (out []byte, etag string, err error) {
if store == "" {
return nil, "", errors.New("nil store")
}
@ -250,13 +255,13 @@ func (c *Client) GetStateWithConsistency(ctx context.Context, store, key string,
return result.Data, result.Etag, nil
}
// GetState retreaves state from specific store using default consistency option.
func (c *Client) GetState(ctx context.Context, store, key string) (out []byte, etag string, err error) {
return c.GetStateWithConsistency(ctx, store, key, StateConsistencyStrong)
// DeleteState deletes content from store using default state options.
func (c *GRPCClient) DeleteState(ctx context.Context, store, key string) error {
return c.DeleteStateVersion(ctx, store, key, "", nil)
}
// DeleteStateVersion deletes content from store using provided state options and etag.
func (c *Client) DeleteStateVersion(ctx context.Context, store, key, etag string, opts *StateOptions) error {
func (c *GRPCClient) DeleteStateVersion(ctx context.Context, store, key, etag string, opts *StateOptions) error {
if store == "" {
return errors.New("nil store")
}
@ -278,8 +283,3 @@ func (c *Client) DeleteStateVersion(ctx context.Context, store, key, etag string
return nil
}
// DeleteState deletes content from store using default state options.
func (c *Client) DeleteState(ctx context.Context, store, key string) error {
return c.DeleteStateVersion(ctx, store, key, "", nil)
}

View File

@ -39,7 +39,7 @@ func TestStateOptionsConverter(t *testing.T) {
func TestSaveStateData(t *testing.T) {
ctx := context.Background()
data := "test"
client, closer := getTestClient(ctx)
client, closer := getTestClient(ctx, t)
defer closer()
err := client.SaveStateData(ctx, "store", "key1", "", []byte(data))

7
go.mod
View File

@ -4,15 +4,14 @@ go 1.14
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gogo/protobuf v1.3.1 // indirect
github.com/golang/protobuf v1.4.2
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.6.1
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 // indirect
golang.org/x/sys v0.0.0-20200620081246-981b61492c35 // indirect
golang.org/x/text v0.3.3 // indirect
google.golang.org/genproto v0.0.0-20200617032506-f1bdc9086088 // indirect
google.golang.org/genproto v0.0.0-20200622133129-d0ee0c36e670 // indirect
google.golang.org/grpc v1.29.1
google.golang.org/protobuf v1.24.0
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
)

17
go.sum
View File

@ -10,8 +10,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -30,8 +28,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -58,13 +54,12 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200620081246-981b61492c35 h1:wb/9mP8eUAmHfkM8RmpeLq6nUA7c2i5+bQOtcDftjaE=
golang.org/x/sys v0.0.0-20200620081246-981b61492c35/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@ -76,8 +71,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200617032506-f1bdc9086088 h1:XXo4PvhJkaWYIkwn7bX7mcdB8RdcOvn12HbaUUAwX3E=
google.golang.org/genproto v0.0.0-20200617032506-f1bdc9086088/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200622133129-d0ee0c36e670 h1:v/N9fZIfu6jopNImrZwgx48fql5yT3k82CJvpIyGtPA=
google.golang.org/genproto v0.0.0-20200622133129-d0ee0c36e670/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@ -97,7 +92,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=