Merge pull request #14000 from vimalk78/e2e-txn-test

tests: Migrate Txn tests to common framework
This commit is contained in:
Marek Siarkowicz 2022-05-11 11:50:07 +02:00 committed by GitHub
commit 5fd69102ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 418 additions and 84 deletions

View File

@ -23,7 +23,7 @@ import (
"strings"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/client/v3"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/pkg/v3/cobrautl"
"github.com/spf13/cobra"
@ -85,7 +85,7 @@ func readCompares(r *bufio.Reader) (cmps []clientv3.Cmp) {
break
}
cmp, err := parseCompare(line)
cmp, err := ParseCompare(line)
if err != nil {
cobrautl.ExitWithError(cobrautl.ExitInvalidInput, err)
}
@ -119,7 +119,7 @@ func readOps(r *bufio.Reader) (ops []clientv3.Op) {
}
func parseRequestUnion(line string) (*clientv3.Op, error) {
args := argify(line)
args := Argify(line)
if len(args) < 2 {
return nil, fmt.Errorf("invalid txn compare request: %s", line)
}
@ -153,7 +153,7 @@ func parseRequestUnion(line string) (*clientv3.Op, error) {
return &op, nil
}
func parseCompare(line string) (*clientv3.Cmp, error) {
func ParseCompare(line string) (*clientv3.Cmp, error) {
var (
key string
op string

View File

@ -27,7 +27,7 @@ import (
"time"
pb "go.etcd.io/etcd/api/v3/mvccpb"
"go.etcd.io/etcd/client/v3"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/pkg/v3/cobrautl"
"github.com/spf13/cobra"
@ -56,7 +56,7 @@ func addHexPrefix(s string) string {
return string(ns)
}
func argify(s string) []string {
func Argify(s string) []string {
r := regexp.MustCompile(`"(?:[^"\\]|\\.)*"|'[^']*'|[^'"\s]\S*[^'"\s]?`)
args := r.FindAllString(s, -1)
for i := range args {

View File

@ -23,7 +23,7 @@ import (
"os/exec"
"strings"
"go.etcd.io/etcd/client/v3"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/pkg/v3/cobrautl"
"github.com/spf13/cobra"
@ -103,7 +103,7 @@ func watchInteractiveFunc(cmd *cobra.Command, osArgs []string, envKey, envRange
}
l = strings.TrimSuffix(l, "\n")
args := argify(l)
args := Argify(l)
if len(args) < 1 {
fmt.Fprintf(os.Stderr, "Invalid command: %s (watch and progress supported)\n", l)
continue

1
go.mod
View File

@ -57,7 +57,6 @@ require (
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect

199
tests/common/txn_test.go Normal file
View File

@ -0,0 +1,199 @@
// Copyright 2022 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package common
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/tests/v3/framework/config"
"go.etcd.io/etcd/tests/v3/framework/testutils"
)
type txnReq struct {
compare []string
ifSucess []string
ifFail []string
results []string
}
func TestTxnSucc(t *testing.T) {
tcs := []struct {
name string
config config.ClusterConfig
}{
{
name: "NoTLS",
config: config.ClusterConfig{ClusterSize: 1},
},
{
name: "PeerTLS",
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS},
},
{
name: "PeerAutoTLS",
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS},
},
{
name: "ClientTLS",
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS},
},
{
name: "ClientAutoTLS",
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS},
},
}
reqs := []txnReq{
{
compare: []string{`value("key1") != "value2"`, `value("key2") != "value1"`},
ifSucess: []string{"get key1", "get key2"},
results: []string{"SUCCESS", "key1", "value1", "key2", "value2"},
},
{
compare: []string{`version("key1") = "1"`, `version("key2") = "1"`},
ifSucess: []string{"get key1", "get key2", `put "key \"with\" space" "value \x23"`},
ifFail: []string{`put key1 "fail"`, `put key2 "fail"`},
results: []string{"SUCCESS", "key1", "value1", "key2", "value2", "OK"},
},
{
compare: []string{`version("key \"with\" space") = "1"`},
ifSucess: []string{`get "key \"with\" space"`},
results: []string{"SUCCESS", `key "with" space`, "value \x23"},
},
}
testRunner.BeforeTest(t)
for _, cfg := range tcs {
t.Run(cfg.name, func(t *testing.T) {
clus := testRunner.NewCluster(t, cfg.config)
defer clus.Close()
cc := clus.Client()
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
if err := cc.Put("key1", "value1", config.PutOptions{}); err != nil {
t.Fatalf("could not create key:%s, value:%s", "key1", "value1")
}
if err := cc.Put("key2", "value2", config.PutOptions{}); err != nil {
t.Fatalf("could not create key:%s, value:%s", "key2", "value2")
}
for _, req := range reqs {
resp, err := cc.Txn(req.compare, req.ifSucess, req.ifFail, config.TxnOptions{
Interactive: true,
})
if err != nil {
t.Errorf("Txn returned error: %s", err)
}
assert.Equal(t, req.results, getRespValues(resp))
}
})
})
}
}
func TestTxnFail(t *testing.T) {
tcs := []struct {
name string
config config.ClusterConfig
}{
{
name: "NoTLS",
config: config.ClusterConfig{ClusterSize: 1},
},
{
name: "PeerTLS",
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS},
},
{
name: "PeerAutoTLS",
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS},
},
{
name: "ClientTLS",
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS},
},
{
name: "ClientAutoTLS",
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS},
},
}
reqs := []txnReq{
{
compare: []string{`version("key") < "0"`},
ifSucess: []string{`put key "success"`},
ifFail: []string{`put key "fail"`},
results: []string{"FAILURE", "OK"},
},
{
compare: []string{`value("key1") != "value1"`},
ifSucess: []string{`put key1 "success"`},
ifFail: []string{`put key1 "fail"`},
results: []string{"FAILURE", "OK"},
},
}
testRunner.BeforeTest(t)
for _, cfg := range tcs {
t.Run(cfg.name, func(t *testing.T) {
clus := testRunner.NewCluster(t, cfg.config)
defer clus.Close()
cc := clus.Client()
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
if err := cc.Put("key1", "value1", config.PutOptions{}); err != nil {
t.Fatalf("could not create key:%s, value:%s", "key1", "value1")
}
for _, req := range reqs {
resp, err := cc.Txn(req.compare, req.ifSucess, req.ifFail, config.TxnOptions{
Interactive: true,
})
if err != nil {
t.Errorf("Txn returned error: %s", err)
}
assert.Equal(t, req.results, getRespValues(resp))
}
})
})
}
}
func getRespValues(r *clientv3.TxnResponse) []string {
ss := []string{}
if r.Succeeded {
ss = append(ss, "SUCCESS")
} else {
ss = append(ss, "FAILURE")
}
for _, resp := range r.Responses {
switch v := resp.Response.(type) {
case *pb.ResponseOp_ResponseDeleteRange:
r := (clientv3.DeleteResponse)(*v.ResponseDeleteRange)
ss = append(ss, fmt.Sprintf("%d", r.Deleted))
case *pb.ResponseOp_ResponsePut:
r := (clientv3.PutResponse)(*v.ResponsePut)
ss = append(ss, "OK")
if r.PrevKv != nil {
ss = append(ss, string(r.PrevKv.Key), string(r.PrevKv.Value))
}
case *pb.ResponseOp_ResponseRange:
r := (clientv3.GetResponse)(*v.ResponseRange)
for _, kv := range r.Kvs {
ss = append(ss, string(kv.Key), string(kv.Value))
}
default:
ss = append(ss, fmt.Sprintf("\"Unknown\" : %q\n", fmt.Sprintf("%+v", v)))
}
}
return ss
}

View File

@ -15,84 +15,9 @@
package e2e
import (
"testing"
"go.etcd.io/etcd/tests/v3/framework/e2e"
)
func TestCtlV3TxnInteractiveSuccess(t *testing.T) {
testCtl(t, txnTestSuccess, withInteractive())
}
func TestCtlV3TxnInteractiveSuccessNoTLS(t *testing.T) {
testCtl(t, txnTestSuccess, withInteractive(), withCfg(*e2e.NewConfigNoTLS()))
}
func TestCtlV3TxnInteractiveSuccessClientTLS(t *testing.T) {
testCtl(t, txnTestSuccess, withInteractive(), withCfg(*e2e.NewConfigClientTLS()))
}
func TestCtlV3TxnInteractiveSuccessPeerTLS(t *testing.T) {
testCtl(t, txnTestSuccess, withInteractive(), withCfg(*e2e.NewConfigPeerTLS()))
}
func TestCtlV3TxnInteractiveFail(t *testing.T) {
testCtl(t, txnTestFail, withInteractive())
}
func txnTestSuccess(cx ctlCtx) {
if err := ctlV3Put(cx, "key1", "value1", ""); err != nil {
cx.t.Fatalf("txnTestSuccess ctlV3Put error (%v)", err)
}
if err := ctlV3Put(cx, "key2", "value2", ""); err != nil {
cx.t.Fatalf("txnTestSuccess ctlV3Put error (%v)", err)
}
rqs := []txnRequests{
{
compare: []string{`value("key1") != "value2"`, `value("key2") != "value1"`},
ifSucess: []string{"get key1", "get key2"},
results: []string{"SUCCESS", "key1", "value1", "key2", "value2"},
},
{
compare: []string{`version("key1") = "1"`, `version("key2") = "1"`},
ifSucess: []string{"get key1", "get key2", `put "key \"with\" space" "value \x23"`},
ifFail: []string{`put key1 "fail"`, `put key2 "fail"`},
results: []string{"SUCCESS", "key1", "value1", "key2", "value2"},
},
{
compare: []string{`version("key \"with\" space") = "1"`},
ifSucess: []string{`get "key \"with\" space"`},
results: []string{"SUCCESS", `key "with" space`, "value \x23"},
},
}
for _, rq := range rqs {
if err := ctlV3Txn(cx, rq); err != nil {
cx.t.Fatal(err)
}
}
}
func txnTestFail(cx ctlCtx) {
if err := ctlV3Put(cx, "key1", "value1", ""); err != nil {
cx.t.Fatalf("txnTestSuccess ctlV3Put error (%v)", err)
}
rqs := []txnRequests{
{
compare: []string{`version("key") < "0"`},
ifSucess: []string{`put key "success"`},
ifFail: []string{`put key "fail"`},
results: []string{"FAILURE", "OK"},
},
{
compare: []string{`value("key1") != "value1"`},
ifSucess: []string{`put key1 "success"`},
ifFail: []string{`put key1 "fail"`},
results: []string{"FAILURE", "OK"},
},
}
for _, rq := range rqs {
if err := ctlV3Txn(cx, rq); err != nil {
cx.t.Fatal(err)
}
}
}
type txnRequests struct {
compare []string
ifSucess []string

View File

@ -43,6 +43,10 @@ type DeleteOptions struct {
End string
}
type TxnOptions struct {
Interactive bool
}
type CompactOption struct {
Physical bool
Timeout time.Duration

View File

@ -0,0 +1,39 @@
// Copyright 2022 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package e2e
import (
"encoding/json"
"testing"
clientv3 "go.etcd.io/etcd/client/v3"
)
func Test_AddTxnResponse(t *testing.T) {
jsonData := `{"header":{"cluster_id":238453183653593855,"member_id":14578408409545168728,"revision":3,"raft_term":2},"succeeded":true,"responses":[{"Response":{"response_range":{"header":{"revision":3},"kvs":[{"key":"a2V5MQ==","create_revision":2,"mod_revision":2,"version":1,"value":"dmFsdWUx"}],"count":1}}},{"Response":{"response_range":{"header":{"revision":3},"kvs":[{"key":"a2V5Mg==","create_revision":3,"mod_revision":3,"version":1,"value":"dmFsdWUy"}],"count":1}}}]}`
var resp clientv3.TxnResponse
AddTxnResponse(&resp, jsonData)
err := json.Unmarshal([]byte(jsonData), &resp)
if err != nil {
t.Errorf("json Unmarshal failed. err: %s", err)
}
enc, err := json.Marshal(resp)
if err != nil {
t.Errorf("json Marshal failed. err: %s", err)
}
if string(enc) != jsonData {
t.Error("could not get original message after encoding")
}
}

View File

@ -17,10 +17,12 @@ package e2e
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"go.etcd.io/etcd/api/v3/authpb"
"go.etcd.io/etcd/api/v3/etcdserverpb"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/tests/v3/framework/config"
)
@ -145,6 +147,101 @@ func (ctl *EtcdctlV3) Delete(key string, o config.DeleteOptions) (*clientv3.Dele
return &resp, err
}
func (ctl *EtcdctlV3) Txn(compares, ifSucess, ifFail []string, o config.TxnOptions) (*clientv3.TxnResponse, error) {
args := ctl.cmdArgs()
args = append(args, "txn")
if o.Interactive {
args = append(args, "--interactive")
}
args = append(args, "-w", "json", "--hex=true")
cmd, err := SpawnCmd(args, nil)
if err != nil {
return nil, err
}
_, err = cmd.Expect("compares:")
if err != nil {
return nil, err
}
for _, cmp := range compares {
if err := cmd.Send(cmp + "\r"); err != nil {
return nil, err
}
}
if err := cmd.Send("\r"); err != nil {
return nil, err
}
_, err = cmd.Expect("success requests (get, put, del):")
if err != nil {
return nil, err
}
for _, req := range ifSucess {
if err = cmd.Send(req + "\r"); err != nil {
return nil, err
}
}
if err = cmd.Send("\r"); err != nil {
return nil, err
}
_, err = cmd.Expect("failure requests (get, put, del):")
if err != nil {
return nil, err
}
for _, req := range ifFail {
if err = cmd.Send(req + "\r"); err != nil {
return nil, err
}
}
if err = cmd.Send("\r"); err != nil {
return nil, err
}
var line string
line, err = cmd.Expect("header")
if err != nil {
return nil, err
}
var resp clientv3.TxnResponse
AddTxnResponse(&resp, line)
err = json.Unmarshal([]byte(line), &resp)
return &resp, err
}
// AddTxnResponse looks for ResponseOp json tags and adds the objects for json decoding
func AddTxnResponse(resp *clientv3.TxnResponse, jsonData string) {
if resp == nil {
return
}
if resp.Responses == nil {
resp.Responses = []*etcdserverpb.ResponseOp{}
}
jd := json.NewDecoder(strings.NewReader(jsonData))
for {
t, e := jd.Token()
if e == io.EOF {
break
}
if t == "response_range" {
resp.Responses = append(resp.Responses, &etcdserverpb.ResponseOp{
Response: &etcdserverpb.ResponseOp_ResponseRange{},
})
}
if t == "response_put" {
resp.Responses = append(resp.Responses, &etcdserverpb.ResponseOp{
Response: &etcdserverpb.ResponseOp_ResponsePut{},
})
}
if t == "response_delete_range" {
resp.Responses = append(resp.Responses, &etcdserverpb.ResponseOp{
Response: &etcdserverpb.ResponseOp_ResponseDeleteRange{},
})
}
if t == "response_txn" {
resp.Responses = append(resp.Responses, &etcdserverpb.ResponseOp{
Response: &etcdserverpb.ResponseOp_ResponseTxn{},
})
}
}
}
func (ctl *EtcdctlV3) MemberList() (*clientv3.MemberListResponse, error) {
cmd, err := SpawnCmd(ctl.cmdArgs("member", "list", "-w", "json"), nil)
if err != nil {

View File

@ -17,6 +17,7 @@ package framework
import (
"context"
"fmt"
"strings"
"testing"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
@ -24,6 +25,7 @@ import (
"go.etcd.io/etcd/client/pkg/v3/testutil"
"go.etcd.io/etcd/client/pkg/v3/transport"
clientv3 "go.etcd.io/etcd/client/v3"
etcdctlcmd "go.etcd.io/etcd/etcdctl/v3/ctlv3/command"
"go.etcd.io/etcd/tests/v3/framework/config"
"go.etcd.io/etcd/tests/v3/framework/integration"
@ -335,3 +337,46 @@ func (c integrationClient) RoleRevokePermission(role string, key, rangeEnd strin
func (c integrationClient) RoleDelete(role string) (*clientv3.AuthRoleDeleteResponse, error) {
return c.Client.RoleDelete(context.Background(), role)
}
func (c integrationClient) Txn(compares, ifSucess, ifFail []string, o config.TxnOptions) (*clientv3.TxnResponse, error) {
txn := c.Client.Txn(context.Background())
cmps := []clientv3.Cmp{}
for _, c := range compares {
cmp, err := etcdctlcmd.ParseCompare(c)
if err != nil {
return nil, err
}
cmps = append(cmps, *cmp)
}
succOps, err := getOps(ifSucess)
if err != nil {
return nil, err
}
failOps, err := getOps(ifFail)
if err != nil {
return nil, err
}
txnrsp, err := txn.
If(cmps...).
Then(succOps...).
Else(failOps...).
Commit()
return txnrsp, err
}
func getOps(ss []string) ([]clientv3.Op, error) {
ops := []clientv3.Op{}
for _, s := range ss {
s = strings.TrimSpace(s)
args := etcdctlcmd.Argify(s)
switch args[0] {
case "get":
ops = append(ops, clientv3.OpGet(args[1]))
case "put":
ops = append(ops, clientv3.OpPut(args[1], args[2]))
case "del":
ops = append(ops, clientv3.OpDelete(args[1]))
}
}
return ops, nil
}

View File

@ -60,10 +60,13 @@ type Client interface {
UserList() (*clientv3.AuthUserListResponse, error)
UserDelete(name string) (*clientv3.AuthUserDeleteResponse, error)
UserChangePass(user, newPass string) error
RoleAdd(name string) (*clientv3.AuthRoleAddResponse, error)
RoleGrantPermission(name string, key, rangeEnd string, permType clientv3.PermissionType) (*clientv3.AuthRoleGrantPermissionResponse, error)
RoleGet(role string) (*clientv3.AuthRoleGetResponse, error)
RoleList() (*clientv3.AuthRoleListResponse, error)
RoleRevokePermission(role string, key, rangeEnd string) (*clientv3.AuthRoleRevokePermissionResponse, error)
RoleDelete(role string) (*clientv3.AuthRoleDeleteResponse, error)
Txn(compares, ifSucess, ifFail []string, o config.TxnOptions) (*clientv3.TxnResponse, error)
}

View File

@ -33,6 +33,7 @@ require (
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
go.etcd.io/etcd/client/v2 v2.306.0-alpha.0
go.etcd.io/etcd/client/v3 v3.6.0-alpha.0
go.etcd.io/etcd/etcdctl/v3 v3.6.0-alpha.0
go.etcd.io/etcd/etcdutl/v3 v3.6.0-alpha.0
go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0
go.etcd.io/etcd/raft/v3 v3.6.0-alpha.0
@ -47,6 +48,7 @@ require (
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
@ -58,7 +60,10 @@ require (
github.com/gorilla/websocket v1.4.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
@ -81,6 +86,7 @@ require (
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect

View File

@ -46,6 +46,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -92,6 +94,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -207,6 +211,13 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@ -217,6 +228,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -424,6 +436,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -449,6 +462,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -607,6 +622,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/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=