Compare commits

..

71 Commits

Author SHA1 Message Date
Kevin Wan
67db40ed4f Revert "feat: enable retry for zrpc (#1237)" (#1245)
This reverts commit 09eb53f308.
2021-11-15 23:30:31 +08:00
FabioCircle
11c485a5ed Duplicate temporary variable (#1244)
Co-authored-by: fabiowzhang <fabiowzhang@wesure.cn>
2021-11-15 23:14:54 +08:00
anqiansong
b0573af9a9 Update template (#1243) 2021-11-15 21:02:11 +08:00
Kevin Wan
09eb53f308 feat: enable retry for zrpc (#1237) 2021-11-14 22:33:01 +08:00
Kevin Wan
11f85d1b80 chore: remove conf.CheckedDuration (#1235) 2021-11-13 23:34:30 +08:00
anqiansong
0cb86c6990 reset link goctl (#1232) 2021-11-13 18:39:07 +08:00
Kevin Wan
57d2f22c24 feat: disable grpc retry, enable it in v1.2.4 (#1233) 2021-11-13 15:38:43 +08:00
Kevin Wan
fa0c364982 fixes #1169 (#1229) 2021-11-12 14:05:28 +08:00
Kevin Wan
a6c8113419 chore: refactor, better goctl message (#1228) 2021-11-11 22:58:33 +08:00
Kevin Wan
4f5c30e083 chore: remove unused const (#1224) 2021-11-10 21:45:42 +08:00
Kevin Wan
9d0b51fa26 fixes #1222 (#1223) 2021-11-10 21:25:51 +08:00
Kevin Wan
ba5f8045a2 Update FUNDING.yml
disable sponsor button.
2021-11-10 21:22:34 +08:00
an
3a510a9138 chore: redislock use stringx.randn replace randomStr func (#1220) 2021-11-10 21:14:21 +08:00
Kevin Wan
d3bfa16813 feat: exit with non-zero code on errors (#1218)
* feat: exit with non-zero code on errors

* chore: use const for code
2021-11-09 22:42:44 +08:00
Kevin Wan
28409791fa feat: support CORS, better implementation (#1217)
* feat: support CORS, better implementation

* chore: refine code
2021-11-09 20:35:57 +08:00
Kevin Wan
c1abe87953 Create FUNDING.yml
add sponsor button
2021-11-09 14:27:36 +08:00
Kevin Wan
f8367856e8 chore: refine code (#1215) 2021-11-08 23:12:13 +08:00
Kevin Wan
a72b0a689b docs: add go-zero users (#1214) 2021-11-08 16:13:24 +08:00
anqiansong
69a4d213a3 Fix issue 1205 (#1211)
* fix #1205

* move builder into stores

* remove xrom

* Remove unused code

* Remove unused code

* refactor builderx to builder

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-11-07 22:44:37 +08:00
Kevin Wan
c28e01fed3 feat: support CORS by using rest.WithCors(...) (#1212)
* feat: support CORS by using rest.WithCors(...)

* chore: add comments

* refactor: lowercase unexported methods

* ci: fix lint errors
2021-11-07 22:42:40 +08:00
Kevin Wan
e8efcef108 update dependencies. (#1210)
* chore: update dependencies

* chore: update dependencies

* chore: update dependencies

* chore: update dependencies

* chore: fix test failure
2021-11-07 16:38:20 +08:00
Kevin Wan
d011316997 test: add more tests (#1209) 2021-11-07 11:41:24 +08:00
Kevin Wan
4d22b0c497 feat: ignore rest.WithPrefix on empty prefix (#1208) 2021-11-06 21:31:35 +08:00
晨曦中
539215d7df goctl docker command add -version (#1206)
* feature(优化): 优化goctl

goctl docker 命令新增version参数,指定builder golang 版本

* feature(优化): 优化goctl

goctl docker 命令新增version参数,指定builder golang 版本
2021-11-06 21:28:32 +08:00
Kevin Wan
3ede597a15 feat: support customizing timeout for specific route (#1203)
* feat: support customizing timeout for specific route

* test: add more tests
2021-11-03 22:20:32 +08:00
anqiansong
01786c5e63 Generate route with prefix (#1200)
* Generate route with prefix

* Update api convert

* Remove TrimSpace

* Update path join

* Format code

* Format code

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-11-03 20:57:03 +08:00
yedf2
6aba5f74fc feat: add NewSessionFromTx to interact with other orm (#1202)
Co-authored-by: yedongfu <dongfuye@163.com>
2021-11-03 20:56:02 +08:00
Kevin Wan
3c894a3fb7 feat: simplify the grpc tls authentication (#1199) 2021-11-02 20:42:22 +08:00
Kevin Wan
1ece3a498f feat: use WithBlock() by default, NonBlock can be set in config or WithNonBlock() (#1198) 2021-11-02 19:02:02 +08:00
Kevin Wan
b76c7ae55d chore: remove semicolon for routes of services in api files (#1195) 2021-11-01 20:37:05 +08:00
Kevin Wan
91b10bd3b9 feat: add rest.WithPrefix to support route prefix (#1194) 2021-11-01 20:15:10 +08:00
Kevin Wan
7e3fe77e7b chore: update goctl version to 1.2.3, prepare for release (#1193)
* feat: slow threshold customizable in rest

* chore: update goctl version to 1.2.3, prepare for release
2021-11-01 18:26:08 +08:00
Kevin Wan
ba43214dae feat: slow threshold customizable in zrpc (#1191)
* feat: slow threshold customizable in rest

* feat: slow threshold customizable in rest

* feat: slow threshold customizable in rest

* feat: slow threshold customizable in zrpc
2021-11-01 15:04:38 +08:00
Kevin Wan
ebc90720ea feat: slow threshold customizable in rest (#1189)
* feat: slow threshold customizable in rest

* feat: slow threshold customizable in rest
2021-11-01 14:48:26 +08:00
Kevin Wan
785d100be9 feat: slow threshold customizable in sqlx (#1188) 2021-11-01 08:37:44 +08:00
Kevin Wan
f13e6f1149 feat: slow threshold customizable in redis (#1187) 2021-11-01 08:20:35 +08:00
Kevin Wan
8be0f77d96 feat: slow threshold customizable in mongo (#1186) 2021-11-01 07:12:53 +08:00
Kevin Wan
429f85a9de feat: slow threshold customizable in redis (#1185)
* feat: slow threshold customizable in redis

* chore: improve config robustness
2021-10-31 22:14:20 +08:00
Kevin Wan
b4d1c6da2c docs: update roadmap (#1184) 2021-10-31 21:00:34 +08:00
Kevin Wan
3c1cfd4c1e feat: support multiple trace agents (#1183)
* feat: support multiple trace agents

* feat: support multiple trace agents, let later calls run if error happens

* test: add more tests
2021-10-31 19:58:01 +08:00
Kevin Wan
a71a210704 feat: let different services start prometheus on demand (#1182) 2021-10-31 18:54:13 +08:00
Kevin Wan
769d06c8ab refactor: simplify tls config in rest (#1181) 2021-10-31 14:10:47 +08:00
Howie
cd1f8da13f [update] add plugin config (#1180)
Signed-off-by: lihaowei <haoweili35@gmail.com>
2021-10-31 12:56:25 +08:00
Kevin Wan
8230474667 test: add more tests (#1179) 2021-10-31 11:33:13 +08:00
Kevin Wan
27f553bf84 docs: update roadmap (#1178) 2021-10-31 11:13:45 +08:00
Kevin Wan
d48bff8c8b docs: add go-zero users (#1176) 2021-10-31 10:02:46 +08:00
Kevin Wan
59b9687f31 feat: support auth account for etcd (#1174) 2021-10-31 09:05:38 +08:00
Kevin Wan
c1a8ccda11 feat: support ssl on zrpc, simplify the config (#1175) 2021-10-30 23:15:39 +08:00
workman-Lu
9df6786b09 support RpcClient Vertify With Unilateralism and Mutual (#647)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2021-10-30 22:07:15 +08:00
anqiansong
bef5bd4e4f fix the package name of grpc client (#1170)
* fix the package name of grpc client

* Remove k8s/utils

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-10-30 21:35:05 +08:00
Kevin Wan
68acfb1891 docs: add go-zero users (#1172) 2021-10-29 21:39:28 +08:00
zeromake
9fd3f752d1 fix(goctl): repeat creation protoc-gen-goctl symlink (#1162) 2021-10-29 09:56:51 +08:00
anqiansong
9c48e9ceab Feature add template version (#1152) 2021-10-29 09:55:41 +08:00
Kevin Wan
bd26783b33 test: add more tests (#1166)
* chore: reverse the order of stopping services

* chore: reverse the order of stopping services

* test: add more tests
2021-10-28 10:04:59 +08:00
Kevin Wan
eda8230521 chore: reorg imports, format code, make MaxRetires default to 0 (#1165)
* chore: reverse the order of stopping services

* chore: reverse the order of stopping services

* chore: reorg imports, format code

* chore: format code, and refactor

* feat: change MaxRetries default to 0, disable retry
2021-10-27 20:57:18 +08:00
chenquan
462ddbb145 Add grpc retry (#1160)
* Add grpc retry

* Update grpc retry

* Add tests

* Fix a bug

* Add api && some tests

* Add comment

* Add double check

* Add server retry quota

* Update optimize code

* Fix bug

* Update optimize code

* Update optimize code

* Fix bug
2021-10-27 19:46:07 +08:00
Kevin Wan
496a2f341e test: add more tests (#1163)
* chore: reverse the order of stopping services

* chore: reverse the order of stopping services

* test: add more tests
2021-10-25 21:10:08 +08:00
Kevin Wan
7109d6d635 chore: reverse the order of stopping services (#1159)
* chore: reverse the order of stopping services

* chore: reverse the order of stopping services
2021-10-24 12:01:17 +08:00
Kevin Wan
ca72241fa3 docs: update qr code (#1158) 2021-10-23 22:12:50 +08:00
Kevin Wan
a6bdffd225 test: add more tests (#1154) 2021-10-21 21:16:18 +08:00
Kevin Wan
5636bf4955 test: add more tests (#1150) 2021-10-20 17:50:01 +08:00
anqiansong
a944a7fd7e Mark deprecated syntax (#1148) 2021-10-20 10:58:25 +08:00
Kevin Wan
a40fa405e4 test: add more tests (#1149) 2021-10-19 23:48:25 +08:00
Kevin Wan
eab77e21dd test: add more tests (#1147)
* test: add more tests

* test: add more tests
2021-10-19 22:37:56 +08:00
Kevin Wan
d41163f5c1 docs: add go-zero users (#1141) 2021-10-18 18:38:01 +08:00
Kevin Wan
265b1f2459 test: add more tests (#1138) 2021-10-15 16:27:30 +08:00
Kevin Wan
c92ea59228 test: add more tests (#1137) 2021-10-15 16:07:38 +08:00
Kevin Wan
afddfea093 docs: add go-zero users (#1135) 2021-10-14 12:50:25 +08:00
Kevin Wan
fa4dc151ca test: add more tests (#1134) 2021-10-13 22:42:54 +08:00
anqiansong
44202acb18 Fix issue #1127 (#1131)
* fix #1127

* fix #1127

* fixed unit test

* add go keyword converter

Co-authored-by: anqiansong <anqiansong@bytedance.com>
2021-10-13 20:48:47 +08:00
Kevin Wan
cf00786209 docs: add go-zero users (#1130) 2021-10-12 22:34:13 +08:00
130 changed files with 2687 additions and 903 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg

3
.gitignore vendored
View File

@@ -15,6 +15,9 @@
**/.DS_Store
**/logs
# for test purpose
adhoc
# gitlab ci
.cache

View File

@@ -10,13 +10,18 @@ We hope that the items listed below will inspire further engagement from the com
## 2021 Q3
- [x] Support `goctl model pg` to support PostgreSQL code generation
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file
- [ ] Adapt builtin tracing mechanism to opentracing solutions
- [x] Adapt builtin tracing mechanism to opentracing solutions
## 2021 Q4
- [x] Support `username/password` authentication in ETCD
- [x] Support `SSL/TLS` in `zRPC`
- [x] Support `TLS` in redis connections
- [ ] Support `retry strategies` in `zRPC`
## 2022
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file
- [ ] Add `httpx.Client` with governance, like circuit breaker etc.
- [ ] Support `goctl doctor` command to report potential issues for given service
- [ ] Support `context` in redis related methods for timeout and tracing
- [ ] Support `context` in sql related methods for timeout and tracing
- [ ] Support `context` in mongodb related methods for timeout and tracing
- [ ] Support TLS in redis connections

View File

@@ -0,0 +1,7 @@
package discov
import "github.com/tal-tech/go-zero/core/discov/internal"
func RegisterAccount(endpoints []string, user, pass string) {
internal.AddAccount(endpoints, user, pass)
}

View File

@@ -0,0 +1,21 @@
package discov
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/stringx"
)
func TestRegisterAccount(t *testing.T) {
endpoints := []string{
"localhost:2379",
}
user := "foo" + stringx.Rand()
RegisterAccount(endpoints, user, "bar")
account, ok := internal.GetAccount(endpoints)
assert.True(t, ok)
assert.Equal(t, user, account.User)
assert.Equal(t, "bar", account.Pass)
}

View File

@@ -6,6 +6,13 @@ import "errors"
type EtcdConf struct {
Hosts []string
Key string
User string `json:",optional"`
Pass string `json:",optional"`
}
// HasAccount returns if account provided.
func (c EtcdConf) HasAccount() bool {
return len(c.User) > 0 && len(c.Pass) > 0
}
// Validate validates c.

View File

@@ -44,3 +44,39 @@ func TestConfig(t *testing.T) {
}
}
}
func TestEtcdConf_HasAccount(t *testing.T) {
tests := []struct {
EtcdConf
hasAccount bool
}{
{
EtcdConf: EtcdConf{
Hosts: []string{"any"},
Key: "key",
},
hasAccount: false,
},
{
EtcdConf: EtcdConf{
Hosts: []string{"any"},
Key: "key",
User: "foo",
},
hasAccount: false,
},
{
EtcdConf: EtcdConf{
Hosts: []string{"any"},
Key: "key",
User: "foo",
Pass: "bar",
},
hasAccount: true,
},
}
for _, test := range tests {
assert.Equal(t, test.hasAccount, test.EtcdConf.HasAccount())
}
}

View File

@@ -0,0 +1,31 @@
package internal
import "sync"
type Account struct {
User string
Pass string
}
var (
accounts = make(map[string]Account)
lock sync.RWMutex
)
func AddAccount(endpoints []string, user, pass string) {
lock.Lock()
defer lock.Unlock()
accounts[getClusterKey(endpoints)] = Account{
User: user,
Pass: pass,
}
}
func GetAccount(endpoints []string) (Account, bool) {
lock.RLock()
defer lock.RUnlock()
account, ok := accounts[getClusterKey(endpoints)]
return account, ok
}

View File

@@ -0,0 +1,34 @@
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stringx"
)
func TestAccount(t *testing.T) {
endpoints := []string{
"192.168.0.2:2379",
"192.168.0.3:2379",
"192.168.0.4:2379",
}
username := "foo" + stringx.Rand()
password := "bar"
anotherPassword := "any"
_, ok := GetAccount(endpoints)
assert.False(t, ok)
AddAccount(endpoints, username, password)
account, ok := GetAccount(endpoints)
assert.True(t, ok)
assert.Equal(t, username, account.User)
assert.Equal(t, password, account.Pass)
AddAccount(endpoints, username, anotherPassword)
account, ok = GetAccount(endpoints)
assert.True(t, ok)
assert.Equal(t, username, account.User)
assert.Equal(t, anotherPassword, account.Pass)
}

View File

@@ -302,14 +302,20 @@ func (c *cluster) watchConnState(cli EtcdClient) {
// DialClient dials an etcd cluster with given endpoints.
func DialClient(endpoints []string) (EtcdClient, error) {
return clientv3.New(clientv3.Config{
cfg := clientv3.Config{
Endpoints: endpoints,
AutoSyncInterval: autoSyncInterval,
DialTimeout: DialTimeout,
DialKeepAliveTime: dialKeepAliveTime,
DialKeepAliveTimeout: DialTimeout,
RejectOldCluster: true,
})
}
if account, ok := GetAccount(endpoints); ok {
cfg.Username = account.User
cfg.Password = account.Pass
}
return clientv3.New(cfg)
}
func getClusterKey(endpoints []string) string {

View File

@@ -33,6 +33,7 @@ func setMockClient(cli EtcdClient) func() {
}
func TestGetCluster(t *testing.T) {
AddAccount([]string{"first"}, "foo", "bar")
c1 := GetRegistry().getCluster([]string{"first"})
c2 := GetRegistry().getCluster([]string{"second"})
c3 := GetRegistry().getCluster([]string{"first"})

View File

@@ -11,8 +11,8 @@ import (
)
type (
// PublisherOption defines the method to customize a Publisher.
PublisherOption func(client *Publisher)
// PubOption defines the method to customize a Publisher.
PubOption func(client *Publisher)
// A Publisher can be used to publish the value to an etcd cluster on the given key.
Publisher struct {
@@ -32,7 +32,7 @@ type (
// endpoints is the hosts of the etcd cluster.
// key:value are a pair to be published.
// opts are used to customize the Publisher.
func NewPublisher(endpoints []string, key, value string, opts ...PublisherOption) *Publisher {
func NewPublisher(endpoints []string, key, value string, opts ...PubOption) *Publisher {
publisher := &Publisher{
endpoints: endpoints,
key: key,
@@ -145,8 +145,15 @@ func (p *Publisher) revoke(cli internal.EtcdClient) {
}
}
// WithPubEtcdAccount provides the etcd username/password.
func WithPubEtcdAccount(user, pass string) PubOption {
return func(pub *Publisher) {
internal.AddAccount(pub.endpoints, user, pass)
}
}
// WithId customizes a Publisher with the id.
func WithId(id int64) PublisherOption {
func WithId(id int64) PubOption {
return func(publisher *Publisher) {
publisher.id = id
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/tal-tech/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stringx"
clientv3 "go.etcd.io/etcd/client/v3"
)
@@ -30,7 +31,8 @@ func TestPublisher_register(t *testing.T) {
ID: id,
}, nil)
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", id), "thevalue", gomock.Any())
pub := NewPublisher(nil, "thekey", "thevalue")
pub := NewPublisher(nil, "thekey", "thevalue",
WithPubEtcdAccount(stringx.Rand(), "bar"))
_, err := pub.register(cli)
assert.Nil(t, err)
}

View File

@@ -9,16 +9,14 @@ import (
)
type (
subOptions struct {
exclusive bool
}
// SubOption defines the method to customize a Subscriber.
SubOption func(opts *subOptions)
SubOption func(sub *Subscriber)
// A Subscriber is used to subscribe the given key on a etcd cluster.
Subscriber struct {
items *container
endpoints []string
exclusive bool
items *container
}
)
@@ -27,14 +25,14 @@ type (
// key is the key to subscribe.
// opts are used to customize the Subscriber.
func NewSubscriber(endpoints []string, key string, opts ...SubOption) (*Subscriber, error) {
var subOpts subOptions
for _, opt := range opts {
opt(&subOpts)
}
sub := &Subscriber{
items: newContainer(subOpts.exclusive),
endpoints: endpoints,
}
for _, opt := range opts {
opt(sub)
}
sub.items = newContainer(sub.exclusive)
if err := internal.GetRegistry().Monitor(endpoints, key, sub.items); err != nil {
return nil, err
}
@@ -55,8 +53,14 @@ func (s *Subscriber) Values() []string {
// Exclusive means that key value can only be 1:1,
// which means later added value will remove the keys associated with the same value previously.
func Exclusive() SubOption {
return func(opts *subOptions) {
opts.exclusive = true
return func(sub *Subscriber) {
sub.exclusive = true
}
}
func WithSubEtcdAccount(user, pass string) SubOption {
return func(sub *Subscriber) {
internal.AddAccount(sub.endpoints, user, pass)
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/stringx"
)
const (
@@ -201,11 +202,9 @@ func TestContainer(t *testing.T) {
}
func TestSubscriber(t *testing.T) {
var opt subOptions
Exclusive()(&opt)
sub := new(Subscriber)
sub.items = newContainer(opt.exclusive)
Exclusive()(sub)
sub.items = newContainer(sub.exclusive)
var count int32
sub.AddListener(func() {
atomic.AddInt32(&count, 1)
@@ -214,3 +213,15 @@ func TestSubscriber(t *testing.T) {
assert.Empty(t, sub.Values())
assert.Equal(t, int32(1), atomic.LoadInt32(&count))
}
func TestWithSubEtcdAccount(t *testing.T) {
endpoints := []string{"localhost:2379"}
user := stringx.Rand()
WithSubEtcdAccount(user, "bar")(&Subscriber{
endpoints: endpoints,
})
account, ok := internal.GetAccount(endpoints)
assert.True(t, ok)
assert.Equal(t, user, account.User)
assert.Equal(t, "bar", account.Pass)
}

View File

@@ -74,12 +74,12 @@ func TestConsistentHashIncrementalTransfer(t *testing.T) {
laterCh := create()
laterCh.AddWithWeight(node, 10*(i+1))
for i := 0; i < requestSize; i++ {
key, ok := laterCh.Get(requestSize + i)
for j := 0; j < requestSize; j++ {
key, ok := laterCh.Get(requestSize + j)
assert.True(t, ok)
assert.NotNil(t, key)
value := key.(string)
assert.True(t, value == keys[i] || value == node)
assert.True(t, value == keys[j] || value == node)
}
}
}

View File

@@ -5,6 +5,7 @@ import (
"testing"
"time"
"github.com/globalsign/mgo/bson"
"github.com/stretchr/testify/assert"
)
@@ -106,3 +107,20 @@ func TestMilliTime_UnmarshalJSON(t *testing.T) {
})
}
}
func TestUnmarshalWithError(t *testing.T) {
var mt MilliTime
assert.NotNil(t, mt.UnmarshalJSON([]byte("hello")))
}
func TestSetBSON(t *testing.T) {
data, err := bson.Marshal(time.Now())
assert.Nil(t, err)
var raw bson.Raw
assert.Nil(t, bson.Unmarshal(data, &raw))
var mt MilliTime
assert.Nil(t, mt.SetBSON(raw))
assert.NotNil(t, mt.SetBSON(bson.Raw{}))
}

View File

@@ -0,0 +1,62 @@
package logx
import (
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/timex"
)
func TestLimitedExecutor_logOrDiscard(t *testing.T) {
tests := []struct {
name string
threshold time.Duration
lastTime time.Duration
discarded uint32
executed bool
}{
{
name: "nil executor",
executed: true,
},
{
name: "regular",
threshold: time.Hour,
lastTime: timex.Now(),
discarded: 10,
executed: false,
},
{
name: "slow",
threshold: time.Duration(1),
lastTime: -1000,
discarded: 10,
executed: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
executor := newLimitedExecutor(0)
executor.threshold = test.threshold
executor.discarded = test.discarded
executor.lastTime.Set(test.lastTime)
var run int32
executor.logOrDiscard(func() {
atomic.AddInt32(&run, 1)
})
if test.executed {
assert.Equal(t, int32(1), atomic.LoadInt32(&run))
} else {
assert.Equal(t, int32(0), atomic.LoadInt32(&run))
assert.Equal(t, test.discarded+1, atomic.LoadUint32(&executor.discarded))
}
})
}
}

View File

@@ -209,6 +209,12 @@ func TestRepr(t *testing.T) {
newMockPtr(),
"mockptr",
},
{
&mockOpacity{
val: 1,
},
"{1}",
},
{
true,
"true",

View File

@@ -6,6 +6,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/utils/io"
)
func TestUnmarshalYamlBytes(t *testing.T) {
@@ -18,6 +19,22 @@ func TestUnmarshalYamlBytes(t *testing.T) {
assert.Equal(t, "liao", c.Name)
}
func TestUnmarshalYamlBytesErrorInput(t *testing.T) {
var c struct {
Name string
}
content := []byte(`liao`)
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
}
func TestUnmarshalYamlBytesEmptyInput(t *testing.T) {
var c struct {
Name string
}
content := []byte(``)
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
}
func TestUnmarshalYamlBytesOptional(t *testing.T) {
var c struct {
Name string
@@ -918,3 +935,18 @@ func TestUnmarshalYamlReaderError(t *testing.T) {
err := UnmarshalYamlReader(reader, &v)
assert.NotNil(t, err)
}
func TestUnmarshalYamlBadReader(t *testing.T) {
var v struct {
Any string
}
err := UnmarshalYamlReader(new(badReader), &v)
assert.NotNil(t, err)
}
type badReader struct{}
func (b *badReader) Read(p []byte) (n int, err error) {
return 0, io.ErrLimitReached
}

View File

@@ -23,11 +23,11 @@ func Enabled() bool {
// StartAgent starts a prometheus agent.
func StartAgent(c Config) {
once.Do(func() {
if len(c.Host) == 0 {
return
}
if len(c.Host) == 0 {
return
}
once.Do(func() {
enabled.Set(true)
threading.GoSafe(func() {
http.Handle(c.Path, promhttp.Handler())

View File

@@ -0,0 +1,32 @@
package backoff
import (
"math/rand"
"time"
)
// Func defines the method to calculate how long to retry.
type Func func(attempt int) time.Duration
// LinearWithJitter waits a set period of time, allowing for jitter (fractional adjustment).
func LinearWithJitter(waitBetween time.Duration, jitterFraction float64) Func {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return func(attempt int) time.Duration {
multiplier := jitterFraction * (r.Float64()*2 - 1)
return time.Duration(float64(waitBetween) * (1 + multiplier))
}
}
// Interval it waits for a fixed period of time between calls.
func Interval(interval time.Duration) Func {
return func(attempt int) time.Duration {
return interval
}
}
// Exponential produces increasing intervals for each attempt.
func Exponential(scalar time.Duration) Func {
return func(attempt int) time.Duration {
return scalar * time.Duration((1<<attempt)>>1)
}
}

View File

@@ -0,0 +1,30 @@
package backoff
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestWaitBetween(t *testing.T) {
fn := Interval(time.Second)
assert.EqualValues(t, time.Second, fn(1))
}
func TestExponential(t *testing.T) {
fn := Exponential(time.Second)
assert.EqualValues(t, time.Second, fn(1))
}
func TestLinearWithJitter(t *testing.T) {
const rounds = 1000000
var total time.Duration
fn := LinearWithJitter(time.Second, 0.5)
for i := 0; i < rounds; i++ {
total += fn(1)
}
// 0.1% tolerance
assert.True(t, total/time.Duration(rounds)-time.Second < time.Millisecond)
}

42
core/retry/options.go Normal file
View File

@@ -0,0 +1,42 @@
package retry
import (
"time"
"github.com/tal-tech/go-zero/core/retry/backoff"
"google.golang.org/grpc/codes"
)
// WithDisable disables the retry behaviour on this call, or this interceptor.
// It's semantically the same to `WithMax(0)`
func WithDisable() *CallOption {
return WithMax(0)
}
// WithMax sets the maximum number of retries on this call, or this interceptor.
func WithMax(maxRetries int) *CallOption {
return &CallOption{apply: func(options *options) {
options.max = maxRetries
}}
}
// WithBackoff sets the `BackoffFunc` used to control time between retries.
func WithBackoff(backoffFunc backoff.Func) *CallOption {
return &CallOption{apply: func(o *options) {
o.backoffFunc = backoffFunc
}}
}
// WithCodes Allow code to be retried.
func WithCodes(retryCodes ...codes.Code) *CallOption {
return &CallOption{apply: func(o *options) {
o.codes = retryCodes
}}
}
// WithPerRetryTimeout timeout for each retry
func WithPerRetryTimeout(timeout time.Duration) *CallOption {
return &CallOption{apply: func(o *options) {
o.perCallTimeout = timeout
}}
}

View File

@@ -0,0 +1,91 @@
package retry
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
func TestRetryWithDisable(t *testing.T) {
opt := &options{}
assert.EqualValues(t, &options{}, parseRetryCallOptions(opt, WithDisable()))
}
func TestRetryWithMax(t *testing.T) {
n := 5
for i := 0; i < n; i++ {
opt := &options{}
assert.EqualValues(t, &options{max: i}, parseRetryCallOptions(opt, WithMax(i)))
}
}
func TestRetryWithBackoff(t *testing.T) {
opt := &options{}
retryCallOptions := parseRetryCallOptions(opt, WithBackoff(func(attempt int) time.Duration {
return time.Millisecond
}))
assert.EqualValues(t, time.Millisecond, retryCallOptions.backoffFunc(1))
}
func TestRetryWithCodes(t *testing.T) {
opt := &options{}
c := []codes.Code{codes.Unknown, codes.NotFound}
options := parseRetryCallOptions(opt, WithCodes(c...))
assert.EqualValues(t, c, options.codes)
}
func TestRetryWithPerRetryTimeout(t *testing.T) {
opt := &options{}
options := parseRetryCallOptions(opt, WithPerRetryTimeout(time.Millisecond))
assert.EqualValues(t, time.Millisecond, options.perCallTimeout)
}
func Test_waitRetryBackoff(t *testing.T) {
logx.Disable()
opt := &options{perCallTimeout: time.Second, backoffFunc: func(attempt int) time.Duration {
return time.Second
}}
logger := logx.WithContext(context.Background())
err := waitRetryBackoff(logger, 1, context.Background(), opt)
assert.NoError(t, err)
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Millisecond)
defer cancelFunc()
err = waitRetryBackoff(logger, 1, ctx, opt)
assert.ErrorIs(t, err, status.FromContextError(context.DeadlineExceeded).Err())
}
func Test_isRetriable(t *testing.T) {
assert.False(t, isRetriable(status.FromContextError(context.DeadlineExceeded).Err(), &options{codes: DefaultRetriableCodes}))
assert.True(t, isRetriable(status.Error(codes.ResourceExhausted, ""), &options{codes: DefaultRetriableCodes}))
assert.False(t, isRetriable(errors.New("error"), &options{}))
}
func Test_perCallContext(t *testing.T) {
opt := &options{perCallTimeout: time.Second, includeRetryHeader: true}
ctx := metadata.NewIncomingContext(context.Background(), map[string][]string{"1": {"1"}})
callContext := perCallContext(ctx, opt, 1)
md, ok := metadata.FromOutgoingContext(callContext)
assert.True(t, ok)
assert.EqualValues(t, metadata.MD{"1": {"1"}, AttemptMetadataKey: {"1"}}, md)
}
func Test_filterCallOptions(t *testing.T) {
grpcEmptyCallOpt := &grpc.EmptyCallOption{}
retryCallOpt := &CallOption{}
options, retryCallOptions := filterCallOptions([]grpc.CallOption{
grpcEmptyCallOpt,
retryCallOpt,
})
assert.EqualValues(t, []grpc.CallOption{grpcEmptyCallOpt}, options)
assert.EqualValues(t, []*CallOption{retryCallOpt}, retryCallOptions)
}

View File

@@ -0,0 +1,189 @@
package retry
import (
"context"
"strconv"
"time"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/retry/backoff"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
const AttemptMetadataKey = "x-retry-attempt"
var (
// DefaultRetriableCodes default retry code
DefaultRetriableCodes = []codes.Code{codes.ResourceExhausted, codes.Unavailable}
// defaultRetryOptions default retry configuration
defaultRetryOptions = &options{
max: 0, // disabled
perCallTimeout: 0, // disabled
includeRetryHeader: true,
codes: DefaultRetriableCodes,
backoffFunc: backoff.LinearWithJitter(50*time.Millisecond /*jitter*/, 0.10),
}
)
type (
// options retry the configuration
options struct {
max int
perCallTimeout time.Duration
includeRetryHeader bool
codes []codes.Code
backoffFunc backoff.Func
}
// CallOption is a grpc.CallOption that is local to grpc retry.
CallOption struct {
grpc.EmptyCallOption // make sure we implement private after() and before() fields so we don't panic.
apply func(opt *options)
}
)
func waitRetryBackoff(logger logx.Logger, attempt int, ctx context.Context, retryOptions *options) error {
var waitTime time.Duration = 0
if attempt > 0 {
waitTime = retryOptions.backoffFunc(attempt)
}
if waitTime > 0 {
timer := time.NewTimer(waitTime)
defer timer.Stop()
logger.Infof("grpc retry attempt: %d, backoff for %v", attempt, waitTime)
select {
case <-ctx.Done():
return status.FromContextError(ctx.Err()).Err()
case <-timer.C:
// double check
err := ctx.Err()
if err != nil {
return status.FromContextError(err).Err()
}
}
}
return nil
}
func isRetriable(err error, retryOptions *options) bool {
errCode := status.Code(err)
if isContextError(err) {
return false
}
for _, code := range retryOptions.codes {
if code == errCode {
return true
}
}
return false
}
func isContextError(err error) bool {
code := status.Code(err)
return code == codes.DeadlineExceeded || code == codes.Canceled
}
func reuseOrNewWithCallOptions(opt *options, retryCallOptions []*CallOption) *options {
if len(retryCallOptions) == 0 {
return opt
}
return parseRetryCallOptions(opt, retryCallOptions...)
}
func parseRetryCallOptions(opt *options, opts ...*CallOption) *options {
for _, option := range opts {
option.apply(opt)
}
return opt
}
func perCallContext(ctx context.Context, callOpts *options, attempt int) context.Context {
if attempt > 0 {
if callOpts.perCallTimeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, callOpts.perCallTimeout)
_ = cancel
}
if callOpts.includeRetryHeader {
cloneMd := extractIncomingAndClone(ctx)
cloneMd.Set(AttemptMetadataKey, strconv.Itoa(attempt))
ctx = metadata.NewOutgoingContext(ctx, cloneMd)
}
}
return ctx
}
func extractIncomingAndClone(ctx context.Context) metadata.MD {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return metadata.MD{}
}
return md.Copy()
}
func filterCallOptions(callOptions []grpc.CallOption) (grpcOptions []grpc.CallOption, retryOptions []*CallOption) {
for _, opt := range callOptions {
if co, ok := opt.(*CallOption); ok {
retryOptions = append(retryOptions, co)
} else {
grpcOptions = append(grpcOptions, opt)
}
}
return grpcOptions, retryOptions
}
func Do(ctx context.Context, call func(ctx context.Context, opts ...grpc.CallOption) error, opts ...grpc.CallOption) error {
logger := logx.WithContext(ctx)
grpcOpts, retryOpts := filterCallOptions(opts)
callOpts := reuseOrNewWithCallOptions(defaultRetryOptions, retryOpts)
if callOpts.max == 0 {
return call(ctx, opts...)
}
var lastErr error
for attempt := 0; attempt <= callOpts.max; attempt++ {
if err := waitRetryBackoff(logger, attempt, ctx, callOpts); err != nil {
return err
}
callCtx := perCallContext(ctx, callOpts, attempt)
lastErr = call(callCtx, grpcOpts...)
if lastErr == nil {
return nil
}
if attempt == 0 {
logger.Errorf("grpc call failed, got err: %v", lastErr)
} else {
logger.Errorf("grpc retry attempt: %d, got err: %v", attempt, lastErr)
}
if isContextError(lastErr) {
if ctx.Err() != nil {
logger.Errorf("grpc retry attempt: %d, parent context error: %v", attempt, ctx.Err())
return lastErr
} else if callOpts.perCallTimeout != 0 {
logger.Errorf("grpc retry attempt: %d, context error from retry call", attempt)
continue
}
}
if !isRetriable(lastErr, callOpts) {
return lastErr
}
}
return lastErr
}

View File

@@ -0,0 +1,24 @@
package retry
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestDo(t *testing.T) {
n := 4
for i := 0; i < n; i++ {
count := 0
err := Do(context.Background(), func(ctx context.Context, opts ...grpc.CallOption) error {
count++
return status.Error(codes.ResourceExhausted, "ResourceExhausted")
}, WithMax(i))
assert.Error(t, err)
assert.Equal(t, i+1, count)
}
}

View File

@@ -26,6 +26,7 @@ type (
}
// A ServiceGroup is a group of services.
// Attention: the starting order of the added services is not guaranteed.
ServiceGroup struct {
services []Service
stopOnce func()
@@ -41,7 +42,8 @@ func NewServiceGroup() *ServiceGroup {
// Add adds service into sg.
func (sg *ServiceGroup) Add(service Service) {
sg.services = append(sg.services, service)
// push front, stop with reverse order.
sg.services = append([]Service{service}, sg.services...)
}
// Start starts the ServiceGroup.

View File

@@ -0,0 +1,63 @@
package builder
import (
"fmt"
"reflect"
"strings"
)
const dbTag = "db"
// RawFieldNames converts golang struct field into slice string.
func RawFieldNames(in interface{}, postgresSql ...bool) []string {
out := make([]string, 0)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
var pg bool
if len(postgresSql) > 0 {
pg = postgresSql[0]
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
if pg {
out = append(out, tagv)
} else {
out = append(out, fmt.Sprintf("`%s`", tagv))
}
} else {
if pg {
out = append(out, fi.Name)
} else {
out = append(out, fmt.Sprintf("`%s`", fi.Name))
}
}
}
return out
}
// PostgreSqlJoin concatenates the given elements into a string.
func PostgreSqlJoin(elems []string) string {
b := new(strings.Builder)
for index, e := range elems {
b.WriteString(fmt.Sprintf("%s = $%d, ", e, index+2))
}
if b.Len() == 0 {
return b.String()
}
return b.String()[0 : b.Len()-2]
}

View File

@@ -0,0 +1,24 @@
package builder
import (
"testing"
"github.com/stretchr/testify/assert"
)
type mockedUser struct {
ID string `db:"id" json:"id,omitempty"`
UserName string `db:"user_name" json:"userName,omitempty"`
Sex int `db:"sex" json:"sex,omitempty"`
UUID string `db:"uuid" uuid:"uuid,omitempty"`
Age int `db:"age" json:"age"`
}
func TestFieldNames(t *testing.T) {
t.Run("new", func(t *testing.T) {
var u mockedUser
out := RawFieldNames(&u)
expected := []string{"`id`", "`user_name`", "`sex`", "`uuid`", "`age`"}
assert.Equal(t, expected, out)
})
}

View File

@@ -667,10 +667,12 @@ func TestRedis_HyperLogLog(t *testing.T) {
assert.NotNil(t, err)
runOnCluster(t, func(cluster Store) {
_, err := cluster.Pfadd("key")
assert.NotNil(t, err)
_, err = cluster.Pfcount("key")
assert.NotNil(t, err)
ok, err := cluster.Pfadd("key", "value")
assert.Nil(t, err)
assert.True(t, ok)
val, err := cluster.Pfcount("key")
assert.Nil(t, err)
assert.Equal(t, int64(1), val)
})
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/tal-tech/go-zero/core/timex"
)
const slowThreshold = time.Millisecond * 500
const defaultSlowThreshold = time.Millisecond * 500
// ErrNotFound is an alias of mgo.ErrNotFound.
var ErrNotFound = mgo.ErrNotFound
@@ -203,7 +203,7 @@ func (c *decoratedCollection) logDuration(method string, duration time.Duration,
if e != nil {
logx.Error(err)
} else if err != nil {
if duration > slowThreshold {
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
c.name, method, err.Error(), string(content))
} else {
@@ -211,7 +211,7 @@ func (c *decoratedCollection) logDuration(method string, duration time.Duration,
c.name, method, err.Error(), string(content))
}
} else {
if duration > slowThreshold {
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
c.name, method, string(content))
} else {

View File

@@ -8,23 +8,14 @@ import (
"github.com/tal-tech/go-zero/core/breaker"
)
type (
options struct {
timeout time.Duration
}
// Option defines the method to customize a mongo model.
Option func(opts *options)
// A Model is a mongo model.
Model struct {
session *concurrentSession
db *mgo.Database
collection string
brk breaker.Breaker
opts []Option
}
)
// A Model is a mongo model.
type Model struct {
session *concurrentSession
db *mgo.Database
collection string
brk breaker.Breaker
opts []Option
}
// MustNewModel returns a Model, exits on errors.
func MustNewModel(url, collection string, opts ...Option) *Model {

View File

@@ -0,0 +1,14 @@
package mongo
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestWithTimeout(t *testing.T) {
o := defaultOptions()
WithTimeout(time.Second)(o)
assert.Equal(t, time.Second, o.timeout)
}

View File

@@ -0,0 +1,29 @@
package mongo
import (
"time"
"github.com/tal-tech/go-zero/core/syncx"
)
var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
type (
options struct {
timeout time.Duration
}
// Option defines the method to customize a mongo model.
Option func(opts *options)
)
// SetSlowThreshold sets the slow threshold.
func SetSlowThreshold(threshold time.Duration) {
slowThreshold.Set(threshold)
}
func defaultOptions() *options {
return &options{
timeout: defaultTimeout,
}
}

View File

@@ -0,0 +1,14 @@
package mongo
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestSetSlowThreshold(t *testing.T) {
assert.Equal(t, defaultSlowThreshold, slowThreshold.Load())
SetSlowThreshold(time.Second)
assert.Equal(t, time.Second, slowThreshold.Load())
}

View File

@@ -57,9 +57,7 @@ func (cs *concurrentSession) putSession(session *mgo.Session) {
}
func (cs *concurrentSession) takeSession(opts ...Option) (*mgo.Session, error) {
o := &options{
timeout: defaultTimeout,
}
o := defaultOptions()
for _, opt := range opts {
opt(o)
}

View File

@@ -17,7 +17,7 @@ type (
Host string
Type string `json:",default=node,options=node|cluster"`
Pass string `json:",optional"`
Tls bool `json:",default=false,options=true|false"`
Tls bool `json:",optional"`
}
// A RedisKeyConf is a redis config with key.

View File

@@ -9,13 +9,13 @@ import (
"github.com/tal-tech/go-zero/core/timex"
)
func process(proc func(red.Cmder) error) func(red.Cmder) error {
func checkDuration(proc func(red.Cmder) error) func(red.Cmder) error {
return func(cmd red.Cmder) error {
start := timex.Now()
defer func() {
duration := timex.Since(start)
if duration > slowThreshold {
if duration > slowThreshold.Load() {
var buf strings.Builder
for i, arg := range cmd.Args() {
if i > 0 {

View File

@@ -9,6 +9,7 @@ import (
red "github.com/go-redis/redis"
"github.com/tal-tech/go-zero/core/breaker"
"github.com/tal-tech/go-zero/core/mapping"
"github.com/tal-tech/go-zero/core/syncx"
)
const (
@@ -21,12 +22,14 @@ const (
blockingQueryTimeout = 5 * time.Second
readWriteTimeout = 2 * time.Second
slowThreshold = time.Millisecond * 100
defaultSlowThreshold = time.Millisecond * 100
)
// ErrNilNode is an error that indicates a nil redis node.
var ErrNilNode = errors.New("nil redis node")
var (
// ErrNilNode is an error that indicates a nil redis node.
ErrNilNode = errors.New("nil redis node")
slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
)
type (
// Option defines the method to customize a Redis.
@@ -1758,6 +1761,11 @@ func Cluster() Option {
}
}
// SetSlowThreshold sets the slow threshold.
func SetSlowThreshold(threshold time.Duration) {
slowThreshold.Set(threshold)
}
// WithPass customizes the given Redis with given password.
func WithPass(pass string) Option {
return func(r *Redis) {

View File

@@ -11,6 +11,7 @@ import (
"github.com/alicebob/miniredis/v2"
red "github.com/go-redis/redis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stringx"
)
func TestRedis_Exists(t *testing.T) {
@@ -186,7 +187,7 @@ func TestRedis_Hscan(t *testing.T) {
key := "hash:test"
fieldsAndValues := make(map[string]string)
for i := 0; i < 1550; i++ {
fieldsAndValues["filed_"+strconv.Itoa(i)] = randomStr(i)
fieldsAndValues["filed_"+strconv.Itoa(i)] = stringx.Randn(i)
}
err := client.Hmset(key, fieldsAndValues)
assert.Nil(t, err)
@@ -256,13 +257,24 @@ func TestRedis_Keys(t *testing.T) {
func TestRedis_HyperLogLog(t *testing.T) {
runOnRedis(t, func(client *Redis) {
client.Ping()
r := New(client.Addr, badType())
_, err := r.Pfadd("key1")
assert.NotNil(t, err)
_, err = r.Pfcount("*")
assert.NotNil(t, err)
err = r.Pfmerge("*")
assert.NotNil(t, err)
r := New(client.Addr)
ok, err := r.Pfadd("key1", "val1")
assert.Nil(t, err)
assert.True(t, ok)
val, err := r.Pfcount("key1")
assert.Nil(t, err)
assert.Equal(t, int64(1), val)
ok, err = r.Pfadd("key2", "val2")
assert.Nil(t, err)
assert.True(t, ok)
val, err = r.Pfcount("key2")
assert.Nil(t, err)
assert.Equal(t, int64(1), val)
err = r.Pfmerge("key1", "key2")
assert.Nil(t, err)
val, err = r.Pfcount("key1")
assert.Nil(t, err)
assert.Equal(t, int64(2), val)
})
}
@@ -539,7 +551,7 @@ func TestRedis_Sscan(t *testing.T) {
key := "list"
var list []string
for i := 0; i < 1550; i++ {
list = append(list, randomStr(i))
list = append(list, stringx.Randn(i))
}
lens, err := client.Sadd(key, list)
assert.Nil(t, err)
@@ -1073,6 +1085,12 @@ func TestRedisGeo(t *testing.T) {
})
}
func TestSetSlowThreshold(t *testing.T) {
assert.Equal(t, defaultSlowThreshold, slowThreshold.Load())
SetSlowThreshold(time.Second)
assert.Equal(t, time.Second, slowThreshold.Load())
}
func TestRedis_WithPass(t *testing.T) {
runOnRedis(t, func(client *Redis) {
err := New(client.Addr, WithPass("any")).Ping()

View File

@@ -32,7 +32,7 @@ func getClient(r *Redis) (*red.Client, error) {
MinIdleConns: idleConns,
TLSConfig: tlsConfig,
})
store.WrapProcess(process)
store.WrapProcess(checkDuration)
return store, nil
})
if err != nil {

View File

@@ -25,7 +25,7 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
MinIdleConns: idleConns,
TLSConfig: tlsConfig,
})
store.WrapProcess(process)
store.WrapProcess(checkDuration)
return store, nil
})

View File

@@ -8,10 +8,10 @@ import (
red "github.com/go-redis/redis"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stringx"
)
const (
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
return "OK"
@@ -45,7 +45,7 @@ func NewRedisLock(store *Redis, key string) *RedisLock {
return &RedisLock{
store: store,
key: key,
id: randomStr(randomLen),
id: stringx.Randn(randomLen),
}
}
@@ -92,11 +92,3 @@ func (rl *RedisLock) Release() (bool, error) {
func (rl *RedisLock) SetExpire(seconds int) {
atomic.StoreUint32(&rl.seconds, uint32(seconds))
}
func randomStr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

View File

@@ -5,10 +5,18 @@ import (
"time"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/timex"
)
const slowThreshold = time.Millisecond * 500
const defaultSlowThreshold = time.Millisecond * 500
var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
// SetSlowThreshold sets the slow threshold.
func SetSlowThreshold(threshold time.Duration) {
slowThreshold.Set(threshold)
}
func exec(conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
stmt, err := format(q, args...)
@@ -19,7 +27,7 @@ func exec(conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
startTime := timex.Now()
result, err := conn.Exec(q, args...)
duration := timex.Since(startTime)
if duration > slowThreshold {
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] exec: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql exec: %s", stmt)
@@ -40,7 +48,7 @@ func execStmt(conn stmtConn, q string, args ...interface{}) (sql.Result, error)
startTime := timex.Now()
result, err := conn.Exec(args...)
duration := timex.Since(startTime)
if duration > slowThreshold {
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] execStmt: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql execStmt: %s", stmt)
@@ -61,7 +69,7 @@ func query(conn sessionConn, scanner func(*sql.Rows) error, q string, args ...in
startTime := timex.Now()
rows, err := conn.Query(q, args...)
duration := timex.Since(startTime)
if duration > slowThreshold {
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] query: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql query: %s", stmt)
@@ -84,7 +92,7 @@ func queryStmt(conn stmtConn, scanner func(*sql.Rows) error, q string, args ...i
startTime := timex.Now()
rows, err := conn.Query(args...)
duration := timex.Since(startTime)
if duration > slowThreshold {
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] queryStmt: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql queryStmt: %s", stmt)

View File

@@ -171,6 +171,12 @@ func TestStmt_query(t *testing.T) {
}
}
func TestSetSlowThreshold(t *testing.T) {
assert.Equal(t, defaultSlowThreshold, slowThreshold.Load())
SetSlowThreshold(time.Second)
assert.Equal(t, time.Second, slowThreshold.Load())
}
type mockedSessionConn struct {
lastInsertId int64
rowsAffected int64
@@ -180,7 +186,7 @@ type mockedSessionConn struct {
func (m *mockedSessionConn) Exec(query string, args ...interface{}) (sql.Result, error) {
if m.delay {
time.Sleep(slowThreshold + time.Millisecond)
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
return mockedResult{
lastInsertId: m.lastInsertId,
@@ -190,7 +196,7 @@ func (m *mockedSessionConn) Exec(query string, args ...interface{}) (sql.Result,
func (m *mockedSessionConn) Query(query string, args ...interface{}) (*sql.Rows, error) {
if m.delay {
time.Sleep(slowThreshold + time.Millisecond)
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
err := errMockedPlaceholder
@@ -209,7 +215,7 @@ type mockedStmtConn struct {
func (m *mockedStmtConn) Exec(args ...interface{}) (sql.Result, error) {
if m.delay {
time.Sleep(slowThreshold + time.Millisecond)
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
return mockedResult{
lastInsertId: m.lastInsertId,
@@ -219,7 +225,7 @@ func (m *mockedStmtConn) Exec(args ...interface{}) (sql.Result, error) {
func (m *mockedStmtConn) Query(args ...interface{}) (*sql.Rows, error) {
if m.delay {
time.Sleep(slowThreshold + time.Millisecond)
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
err := errMockedPlaceholder

View File

@@ -19,6 +19,12 @@ type (
}
)
// NewSessionFromTx returns a Session with the given sql.Tx.
// Use it with caution, it's provided for other ORM to interact with.
func NewSessionFromTx(tx *sql.Tx) Session {
return txSession{Tx: tx}
}
func (t txSession) Exec(q string, args ...interface{}) (sql.Result, error) {
return exec(t.Tx, q, args...)
}

View File

@@ -41,3 +41,10 @@ func TestFakeTicker(t *testing.T) {
assert.Nil(t, ticker.Wait(time.Second))
assert.Equal(t, int32(total), atomic.LoadInt32(&count))
}
func TestFakeTickerTimeout(t *testing.T) {
ticker := NewFakeTicker()
defer ticker.Stop()
assert.NotNil(t, ticker.Wait(time.Millisecond))
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"sync"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
@@ -19,17 +20,31 @@ const (
kindZipkin = "zipkin"
)
var once sync.Once
var (
agents = make(map[string]lang.PlaceholderType)
lock sync.Mutex
)
// StartAgent starts a opentelemetry agent.
func StartAgent(c Config) {
once.Do(func() {
startAgent(c)
})
lock.Lock()
defer lock.Unlock()
_, ok := agents[c.Endpoint]
if ok {
return
}
// if error happens, let later calls run.
if err := startAgent(c); err != nil {
return
}
agents[c.Endpoint] = lang.Placeholder
}
func createExporter(c Config) (sdktrace.SpanExporter, error) {
// Just support jaeger now, more for later
// Just support jaeger and zipkin now, more for later
switch c.Batcher {
case kindJaeger:
return jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(c.Endpoint)))
@@ -40,7 +55,7 @@ func createExporter(c Config) (sdktrace.SpanExporter, error) {
}
}
func startAgent(c Config) {
func startAgent(c Config) error {
opts := []sdktrace.TracerProviderOption{
// Set the sampling rate based on the parent span to 100%
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(c.Sampler))),
@@ -52,7 +67,7 @@ func startAgent(c Config) {
exp, err := createExporter(c)
if err != nil {
logx.Error(err)
return
return err
}
// Always be sure to batch in production.
@@ -66,4 +81,6 @@ func startAgent(c Config) {
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
logx.Errorf("[otel] error: %v", err)
}))
return nil
}

54
core/trace/agent_test.go Normal file
View File

@@ -0,0 +1,54 @@
package trace
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
)
func TestStartAgent(t *testing.T) {
logx.Disable()
const (
endpoint1 = "localhost:1234"
endpoint2 = "remotehost:1234"
endpoint3 = "localhost:1235"
)
c1 := Config{
Name: "foo",
}
c2 := Config{
Name: "bar",
Endpoint: endpoint1,
Batcher: kindJaeger,
}
c3 := Config{
Name: "any",
Endpoint: endpoint2,
Batcher: kindZipkin,
}
c4 := Config{
Name: "bla",
Endpoint: endpoint3,
Batcher: "otlp",
}
StartAgent(c1)
StartAgent(c1)
StartAgent(c2)
StartAgent(c3)
StartAgent(c4)
lock.Lock()
defer lock.Unlock()
// because remotehost cannot be resolved
assert.Equal(t, 2, len(agents))
_, ok := agents[""]
assert.True(t, ok)
_, ok = agents[endpoint1]
assert.True(t, ok)
_, ok = agents[endpoint2]
assert.False(t, ok)
}

View File

@@ -34,7 +34,7 @@ var (
RPCMessageTypeReceived = RPCMessageTypeKey.String("RECEIVED")
)
// StatusCodeAttr returns a attribute.KeyValue that represents the give c.
// StatusCodeAttr returns an attribute.KeyValue that represents the give c.
func StatusCodeAttr(c gcodes.Code) attribute.KeyValue {
return GRPCStatusCodeKey.Int64(int64(c))
}

View File

@@ -0,0 +1,12 @@
package trace
import (
"testing"
"github.com/stretchr/testify/assert"
gcodes "google.golang.org/grpc/codes"
)
func TestStatusCodeAttr(t *testing.T) {
assert.Equal(t, GRPCStatusCodeKey.Int(int(gcodes.DataLoss)), StatusCodeAttr(gcodes.DataLoss))
}

View File

@@ -157,7 +157,8 @@ func TestExtractValidTraceContext(t *testing.T) {
}),
},
}
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{}))
propagator := otel.GetTextMapPropagator()
for _, tt := range tests {
@@ -242,7 +243,8 @@ func TestExtractInvalidTraceContext(t *testing.T) {
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-",
},
}
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{}))
propagator := otel.GetTextMapPropagator()
for _, tt := range tests {
@@ -308,7 +310,8 @@ func TestInjectValidTraceContext(t *testing.T) {
}),
},
}
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{}))
propagator := otel.GetTextMapPropagator()
for _, tt := range tests {
@@ -325,6 +328,11 @@ func TestInjectValidTraceContext(t *testing.T) {
md := metadata.MD{}
Inject(ctx, propagator, &md)
assert.Equal(t, want, md)
mm := &metadataSupplier{
metadata: &md,
}
assert.NotEmpty(t, mm.Keys())
})
}
}
@@ -334,7 +342,8 @@ func TestInvalidSpanContextDropped(t *testing.T) {
require.False(t, invalidSC.IsValid())
ctx := trace.ContextWithRemoteSpanContext(context.Background(), invalidSC)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{}))
propagator := otel.GetTextMapPropagator()
md := metadata.MD{}
@@ -342,5 +351,6 @@ func TestInvalidSpanContextDropped(t *testing.T) {
mm := &metadataSupplier{
metadata: &md,
}
assert.Empty(t, mm.Keys())
assert.Equal(t, "", mm.Get("traceparent"), "injected invalid SpanContext")
}

View File

@@ -15,7 +15,7 @@ const localhost = "127.0.0.1"
// PeerFromCtx returns the peer from ctx.
func PeerFromCtx(ctx context.Context) string {
p, ok := peer.FromContext(ctx)
if !ok {
if !ok || p == nil {
return ""
}
@@ -55,7 +55,7 @@ func ParseFullMethod(fullMethod string) (string, []attribute.KeyValue) {
func PeerAttr(addr string) []attribute.KeyValue {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return []attribute.KeyValue(nil)
return nil
}
if len(host) == 0 {

View File

@@ -1,13 +1,53 @@
package trace
import (
"context"
"net"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"google.golang.org/grpc/peer"
)
func TestPeerFromContext(t *testing.T) {
addrs, err := net.InterfaceAddrs()
assert.Nil(t, err)
assert.NotEmpty(t, addrs)
tests := []struct {
name string
ctx context.Context
empty bool
}{
{
name: "empty",
ctx: context.Background(),
empty: true,
},
{
name: "nil",
ctx: peer.NewContext(context.Background(), nil),
empty: true,
},
{
name: "with value",
ctx: peer.NewContext(context.Background(), &peer.Peer{
Addr: addrs[0],
}),
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
addr := PeerFromCtx(test.ctx)
assert.Equal(t, test.empty, len(addr) == 0)
})
}
}
func TestParseFullMethod(t *testing.T) {
tests := []struct {
fullMethod string
@@ -68,3 +108,46 @@ func TestParseFullMethod(t *testing.T) {
assert.Equal(t, test.attr, a)
}
}
func TestSpanInfo(t *testing.T) {
val, kvs := SpanInfo("/fullMethod", "remote")
assert.Equal(t, "fullMethod", val)
assert.NotEmpty(t, kvs)
}
func TestPeerAttr(t *testing.T) {
tests := []struct {
name string
addr string
expect []attribute.KeyValue
}{
{
name: "empty",
},
{
name: "port only",
addr: ":8080",
expect: []attribute.KeyValue{
semconv.NetPeerIPKey.String(localhost),
semconv.NetPeerPortKey.String("8080"),
},
},
{
name: "port only",
addr: "192.168.0.2:8080",
expect: []attribute.KeyValue{
semconv.NetPeerIPKey.String("192.168.0.2"),
semconv.NetPeerPortKey.String("8080"),
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
kvs := PeerAttr(test.addr)
assert.EqualValues(t, test.expect, kvs)
})
}
}

58
go.mod
View File

@@ -3,55 +3,51 @@ module github.com/tal-tech/go-zero
go 1.14
require (
github.com/ClickHouse/clickhouse-go v1.4.3
github.com/DATA-DOG/go-sqlmock v1.4.1
github.com/alicebob/miniredis/v2 v2.14.1
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/emicklei/proto v1.9.0
github.com/ClickHouse/clickhouse-go v1.5.1
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/alicebob/miniredis/v2 v2.16.0
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/emicklei/proto v1.9.1
github.com/fatih/color v1.9.0 // indirect
github.com/fatih/structtag v1.2.0
github.com/frankban/quicktest v1.7.2 // indirect
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
github.com/go-redis/redis v6.15.7+incompatible
github.com/go-sql-driver/mysql v1.5.0
github.com/go-xorm/builder v0.3.4
github.com/go-redis/redis v6.15.9+incompatible
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt v3.2.1+incompatible
github.com/golang/mock v1.4.3
github.com/google/uuid v1.1.2
github.com/iancoleman/strcase v0.1.2
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0
github.com/iancoleman/strcase v0.2.0
github.com/justinas/alice v1.2.0
github.com/lib/pq v1.3.0
github.com/lib/pq v1.10.3
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/olekukonko/tablewriter v0.0.5
github.com/pierrec/lz4 v2.5.1+incompatible // indirect
github.com/openzipkin/zipkin-go v0.3.0 // indirect
github.com/prometheus/client_golang v1.11.0
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.7.0
github.com/urfave/cli v1.22.5
github.com/zeromicro/antlr v0.0.1
github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348
github.com/zeromicro/protobuf v0.0.0-20210921042113-636cd51f0c35
go.etcd.io/etcd/api/v3 v3.5.0
go.etcd.io/etcd/client/v3 v3.5.0
go.opentelemetry.io/otel v1.0.1
go.opentelemetry.io/otel/exporters/jaeger v1.0.1
go.opentelemetry.io/otel/exporters/zipkin v1.0.1
go.opentelemetry.io/otel/sdk v1.0.1
go.opentelemetry.io/otel/trace v1.0.1
go.uber.org/automaxprocs v1.3.0
go.etcd.io/etcd/api/v3 v3.5.1
go.etcd.io/etcd/client/v3 v3.5.1
go.opentelemetry.io/otel v1.1.0
go.opentelemetry.io/otel/exporters/jaeger v1.1.0
go.opentelemetry.io/otel/exporters/zipkin v1.1.0
go.opentelemetry.io/otel/sdk v1.1.0
go.opentelemetry.io/otel/trace v1.1.0
go.uber.org/automaxprocs v1.4.0
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b // indirect
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9 // indirect
google.golang.org/grpc v1.41.0
google.golang.org/grpc v1.42.0
google.golang.org/protobuf v1.27.1
gopkg.in/cheggaaa/pb.v1 v1.0.28
gopkg.in/h2non/gock.v1 v1.0.15
gopkg.in/h2non/gock.v1 v1.1.2
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.20.10
k8s.io/apimachinery v0.20.10
k8s.io/client-go v0.20.10
k8s.io/api v0.20.12
k8s.io/apimachinery v0.20.12
k8s.io/client-go v0.20.12
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
)

191
go.sum
View File

@@ -32,16 +32,18 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/clickhouse-go v1.4.3 h1:iAFMa2UrQdR5bHJ2/yaSLffZkxpcOYQMCUuKeNXGdqc=
github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM=
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/ClickHouse/clickhouse-go v1.5.1 h1:I8zVFZTz80crCs0FFEBJooIxsPcV0xfthzK1YrkpJTc=
github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -49,8 +51,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.14.1 h1:GjlbSeoJ24bzdLRs13HoMEeaRZx9kg5nHoRW7QV/nCs=
github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg=
github.com/alicebob/miniredis/v2 v2.16.0 h1:ALkyFg7bSTEd1Mkrb4ppq4fnwjklA59dVtIehXCUZkU=
github.com/alicebob/miniredis/v2 v2.16.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec h1:EEyRvzmpEUZ+I8WmD5cw/vY8EqhambkOqy5iFr0908A=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
@@ -74,15 +76,19 @@ github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -91,12 +97,13 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/proto v1.9.0 h1:l0QiNT6Qs7Yj0Mb4X6dnWBQer4ebei2BFcgQLbGqUDc=
github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/emicklei/proto v1.9.1 h1:MUgjFo5xlMwYv72TnF5xmmdKZ04u+dVbv6wdARv16D8=
github.com/emicklei/proto v1.9.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
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=
@@ -111,8 +118,9 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -139,16 +147,13 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY=
github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -165,8 +170,8 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -184,6 +189,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -207,28 +213,38 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U=
github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -243,19 +259,22 @@ github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -285,24 +304,34 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
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/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.2.5 h1:UwtQQx2pyPIgWYHRg+epgdx1/HnBQTgN3/oIYEJTQzU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
github.com/openzipkin/zipkin-go v0.3.0 h1:XtuXmOLIXLjiU2XduuWREDT0LOKtSgos/g7i7RYyoZQ=
github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.1+incompatible h1:Yq0up0149Hh5Ekhm/91lgkZuD1ZDnXNM26bycpTzYBM=
github.com/pierrec/lz4 v2.5.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -329,7 +358,9 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -339,6 +370,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
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/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -360,42 +392,44 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk=
github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348 h1:OhxL9tn28gDeJVzreIUiE5oVxZCjL3tBJ0XBNw8p5R8=
github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
github.com/zeromicro/protobuf v0.0.0-20210921042113-636cd51f0c35 h1:LKLiozf78evAX4+82AHw/rG7TvefD7TJII7XB7Oip7o=
github.com/zeromicro/protobuf v0.0.0-20210921042113-636cd51f0c35/go.mod h1:CJvyESptK/6uU5003fPJQ9Hmubl3vRuGqaLgzUU0R7Y=
go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/api/v3 v3.5.1 h1:v28cktvBq+7vGyJXF8G+rWJmj+1XUmMtqcLnH8hDocM=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1 h1:XIQcHCFSG53bJETYeRJtIxdLv2EWRGxcfzR8lSnTH4E=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.1 h1:oImGuV5LGKjCqXdjkMHCyWa5OO1gYKCnC/1sgdfj1Uk=
go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.0.1 h1:4XKyXmfqJLOQ7feyV5DB6gsBFZ0ltB8vLtp6pj4JIcc=
go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU=
go.opentelemetry.io/otel/exporters/jaeger v1.0.1 h1:fg9udWIWWJMAT+Gq2ATFd/DFy3OZvKEZy9VK2amxvkw=
go.opentelemetry.io/otel/exporters/jaeger v1.0.1/go.mod h1:85Ym3qknJdIdfRzYS9Ofy9NeLi9gKPFzFDBEHCKpfXI=
go.opentelemetry.io/otel/exporters/zipkin v1.0.1 h1:Li6OvM1Po5qrP+HnXlZa+FyLkMun7JG4R0vTAch12qs=
go.opentelemetry.io/otel/exporters/zipkin v1.0.1/go.mod h1:KXb2W6IVINSd/rKugSARqP3TsByxngvea3B1vm5ju74=
go.opentelemetry.io/otel/sdk v1.0.1 h1:wXxFEWGo7XfXupPwVJvTBOaPBC9FEg0wB8hMNrKk+cA=
go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI=
go.opentelemetry.io/otel/trace v1.0.1 h1:StTeIH6Q3G4r0Fiw34LTokUFESZgIDUr0qIJ7mKmAfw=
go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk=
go.opentelemetry.io/otel v1.1.0 h1:8p0uMLcyyIx0KHNTgO8o3CW8A1aA+dJZJW6PvnMz0Wc=
go.opentelemetry.io/otel v1.1.0/go.mod h1:7cww0OW51jQ8IaZChIEdqLwgh+44+7uiTdWsAL0wQpA=
go.opentelemetry.io/otel/exporters/jaeger v1.1.0 h1:VRF+Hf3EePFO6ab7/wfPoyWzSY4z5X0tTvQtV9/Mq8Y=
go.opentelemetry.io/otel/exporters/jaeger v1.1.0/go.mod h1:D/GIBwAdrFTTqCy1iITpC9nh5rgJpIbFVgkhlz2vCXk=
go.opentelemetry.io/otel/exporters/zipkin v1.1.0 h1:NfP5auMWoVOYnAeQPY+fxNG8UMAu94heSL4rtOL8Bsg=
go.opentelemetry.io/otel/exporters/zipkin v1.1.0/go.mod h1:LZwDnf1mVGTPMq9hdRUHfFBH30SuQvZ1BJaVywpg0VI=
go.opentelemetry.io/otel/sdk v1.1.0 h1:j/1PngUJIDOddkCILQYTevrTIbWd494djgGkSsMit+U=
go.opentelemetry.io/otel/sdk v1.1.0/go.mod h1:3aQvM6uLm6C4wJpHtT8Od3vNzeZ34Pqc6bps8MywWzo=
go.opentelemetry.io/otel/trace v1.1.0 h1:N25T9qCL0+7IpOT8RrRy0WYlL7y6U0WiUJzXcVdXY/o=
go.opentelemetry.io/otel/trace v1.1.0/go.mod h1:i47XtdcBQiktu5IsrPqOHe8w+sBmnLwwHt8wiUsWGTI=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.3.0 h1:II28aZoGdaglS5vVNnspf28lnZpXScxtIozx1lAjdb0=
go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
@@ -407,8 +441,10 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -463,11 +499,15 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -501,9 +541,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
@@ -520,6 +562,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -527,15 +570,16 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d h1:SABT8Vei3iTiu+Gy8KOzpSNz+W1EQ5YBCRtiEETxF+0=
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d/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-20211106132015-ebca88c72f68 h1:Ywe/f3fNleF8I6F6qv3MeFoSZ6CTf2zBMMa/7qVML8M=
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
@@ -578,7 +622,9 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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=
@@ -637,8 +683,9 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -655,15 +702,15 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@@ -686,12 +733,12 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.20.10 h1:kAdgi1zcyenV88/uVEzS9B/fn1m4KRbmdKB0Lxl6z/M=
k8s.io/api v0.20.10/go.mod h1:0kei3F6biGjtRQBo5dUeujq6Ji3UCh9aOSfp/THYd7I=
k8s.io/apimachinery v0.20.10 h1:GcFwz5hsGgKLohcNgv8GrInk60vUdFgBXW7uOY1i1YM=
k8s.io/apimachinery v0.20.10/go.mod h1:kQa//VOAwyVwJ2+L9kOREbsnryfsGSkSM1przND4+mw=
k8s.io/client-go v0.20.10 h1:TgAL2pqcNWMH4eZoS9Uw0BLh2lu5a2A4pmegjp5pmsk=
k8s.io/client-go v0.20.10/go.mod h1:fFg+aLoasv/R+xiVaWjxeqGFYltzgQcOQzkFaSRfnJ0=
k8s.io/api v0.20.12 h1:LfRpmRkJLwPP8eaYehsVVmIIfg1yCBIIUHaSsdqCgHA=
k8s.io/api v0.20.12/go.mod h1:A2brwyEkVLM3wQGNnzoAa5JsQRzHK0uoOQ+bsnv7V68=
k8s.io/apimachinery v0.20.12 h1:2c0LIVNMvB8k2Ozstmhl2zGeCEcPazznuLYEwxFdNjM=
k8s.io/apimachinery v0.20.12/go.mod h1:uM7hCI0NyBymUwgshMgZyte475lxhr+QH6h3cvdnzEc=
k8s.io/client-go v0.20.12 h1:U75SxTC31BHT9i7CbX/hL4v+U1Wkzy/E1vt5ClDPp3I=
k8s.io/client-go v0.20.12/go.mod h1:NBJj6Evp73Xy/4v/O/RDRaH0+3JoxNfjRxkyRgrdbsA=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=

View File

@@ -226,6 +226,12 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
>40. 马鞍山百助网络科技有限公司
>41. 上海阿莫尔科技有限公司
>42. 发明者量化
>43. 济南超级盟网络科技有限公司
>44. 苏州互盟信息存储技术有限公司
>45. 成都艾途教育科技集团有限公司
>46. 上海游族网络
>47. 深信服
>48. 中免日上科技互联有限公司
如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
@@ -242,7 +248,7 @@ go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape
`go-zero` 相关文章和视频都会在 `微服务实践` 公众号整理呈现,欢迎扫码关注 👏
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/wechat-micro.jpg" alt="wechat" width="300" />
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/zeromicro.jpg" alt="wechat" width="600" />
## 11. 微信交流群

View File

@@ -35,7 +35,7 @@ type (
KeyFile string `json:",optional"`
Verbose bool `json:",optional"`
MaxConns int `json:",default=10000"`
MaxBytes int64 `json:",default=1048576,range=[0:33554432]"`
MaxBytes int64 `json:",default=1048576"`
// milliseconds
Timeout int64 `json:",default=3000"`
CpuThreshold int64 `json:",default=900,range=[0:1000]"`

View File

@@ -1,6 +1,7 @@
package rest
import (
"crypto/tls"
"errors"
"fmt"
"net/http"
@@ -13,7 +14,6 @@ import (
"github.com/tal-tech/go-zero/rest/handler"
"github.com/tal-tech/go-zero/rest/httpx"
"github.com/tal-tech/go-zero/rest/internal"
"github.com/tal-tech/go-zero/rest/router"
)
// use 1000m to represent 100%
@@ -30,6 +30,7 @@ type engine struct {
middlewares []Middleware
shedder load.Shedder
priorityShedder load.Shedder
tlsConfig *tls.Config
}
func newEngine(c RestConf) *engine {
@@ -45,58 +46,34 @@ func newEngine(c RestConf) *engine {
return srv
}
func (s *engine) AddRoutes(r featuredRoutes) {
s.routes = append(s.routes, r)
func (ng *engine) addRoutes(r featuredRoutes) {
ng.routes = append(ng.routes, r)
}
func (s *engine) SetUnauthorizedCallback(callback handler.UnauthorizedCallback) {
s.unauthorizedCallback = callback
}
func (s *engine) SetUnsignedCallback(callback handler.UnsignedCallback) {
s.unsignedCallback = callback
}
func (s *engine) Start() error {
return s.StartWithRouter(router.NewRouter())
}
func (s *engine) StartWithRouter(router httpx.Router) error {
if err := s.bindRoutes(router); err != nil {
return err
}
if len(s.conf.CertFile) == 0 && len(s.conf.KeyFile) == 0 {
return internal.StartHttp(s.conf.Host, s.conf.Port, router)
}
return internal.StartHttps(s.conf.Host, s.conf.Port, s.conf.CertFile, s.conf.KeyFile, router)
}
func (s *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain,
func (ng *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain,
verifier func(alice.Chain) alice.Chain) alice.Chain {
if fr.jwt.enabled {
if len(fr.jwt.prevSecret) == 0 {
chain = chain.Append(handler.Authorize(fr.jwt.secret,
handler.WithUnauthorizedCallback(s.unauthorizedCallback)))
handler.WithUnauthorizedCallback(ng.unauthorizedCallback)))
} else {
chain = chain.Append(handler.Authorize(fr.jwt.secret,
handler.WithPrevSecret(fr.jwt.prevSecret),
handler.WithUnauthorizedCallback(s.unauthorizedCallback)))
handler.WithUnauthorizedCallback(ng.unauthorizedCallback)))
}
}
return verifier(chain)
}
func (s *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {
verifier, err := s.signatureVerifier(fr.signature)
func (ng *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {
verifier, err := ng.signatureVerifier(fr.signature)
if err != nil {
return err
}
for _, route := range fr.routes {
if err := s.bindRoute(fr, router, metrics, route, verifier); err != nil {
if err := ng.bindRoute(fr, router, metrics, route, verifier); err != nil {
return err
}
}
@@ -104,24 +81,24 @@ func (s *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metr
return nil
}
func (s *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,
func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,
route Route, verifier func(chain alice.Chain) alice.Chain) error {
chain := alice.New(
handler.TracingHandler(s.conf.Name, route.Path),
s.getLogHandler(),
handler.TracingHandler(ng.conf.Name, route.Path),
ng.getLogHandler(),
handler.PrometheusHandler(route.Path),
handler.MaxConns(s.conf.MaxConns),
handler.MaxConns(ng.conf.MaxConns),
handler.BreakerHandler(route.Method, route.Path, metrics),
handler.SheddingHandler(s.getShedder(fr.priority), metrics),
handler.TimeoutHandler(time.Duration(s.conf.Timeout)*time.Millisecond),
handler.SheddingHandler(ng.getShedder(fr.priority), metrics),
handler.TimeoutHandler(ng.checkedTimeout(fr.timeout)),
handler.RecoverHandler,
handler.MetricHandler(metrics),
handler.MaxBytesHandler(s.conf.MaxBytes),
handler.MaxBytesHandler(ng.conf.MaxBytes),
handler.GunzipHandler,
)
chain = s.appendAuthHandler(fr, chain, verifier)
chain = ng.appendAuthHandler(fr, chain, verifier)
for _, middleware := range s.middlewares {
for _, middleware := range ng.middlewares {
chain = chain.Append(convertMiddleware(middleware))
}
handle := chain.ThenFunc(route.Handler)
@@ -129,11 +106,11 @@ func (s *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat
return router.Handle(route.Method, route.Path, handle)
}
func (s *engine) bindRoutes(router httpx.Router) error {
metrics := s.createMetrics()
func (ng *engine) bindRoutes(router httpx.Router) error {
metrics := ng.createMetrics()
for _, fr := range s.routes {
if err := s.bindFeaturedRoutes(router, fr, metrics); err != nil {
for _, fr := range ng.routes {
if err := ng.bindFeaturedRoutes(router, fr, metrics); err != nil {
return err
}
}
@@ -141,35 +118,55 @@ func (s *engine) bindRoutes(router httpx.Router) error {
return nil
}
func (s *engine) createMetrics() *stat.Metrics {
func (ng *engine) checkedTimeout(timeout time.Duration) time.Duration {
if timeout > 0 {
return timeout
}
return time.Duration(ng.conf.Timeout) * time.Millisecond
}
func (ng *engine) createMetrics() *stat.Metrics {
var metrics *stat.Metrics
if len(s.conf.Name) > 0 {
metrics = stat.NewMetrics(s.conf.Name)
if len(ng.conf.Name) > 0 {
metrics = stat.NewMetrics(ng.conf.Name)
} else {
metrics = stat.NewMetrics(fmt.Sprintf("%s:%d", s.conf.Host, s.conf.Port))
metrics = stat.NewMetrics(fmt.Sprintf("%s:%d", ng.conf.Host, ng.conf.Port))
}
return metrics
}
func (s *engine) getLogHandler() func(http.Handler) http.Handler {
if s.conf.Verbose {
func (ng *engine) getLogHandler() func(http.Handler) http.Handler {
if ng.conf.Verbose {
return handler.DetailedLogHandler
}
return handler.LogHandler
}
func (s *engine) getShedder(priority bool) load.Shedder {
if priority && s.priorityShedder != nil {
return s.priorityShedder
func (ng *engine) getShedder(priority bool) load.Shedder {
if priority && ng.priorityShedder != nil {
return ng.priorityShedder
}
return s.shedder
return ng.shedder
}
func (s *engine) signatureVerifier(signature signatureSetting) (func(chain alice.Chain) alice.Chain, error) {
func (ng *engine) setTlsConfig(cfg *tls.Config) {
ng.tlsConfig = cfg
}
func (ng *engine) setUnauthorizedCallback(callback handler.UnauthorizedCallback) {
ng.unauthorizedCallback = callback
}
func (ng *engine) setUnsignedCallback(callback handler.UnsignedCallback) {
ng.unsignedCallback = callback
}
func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain alice.Chain) alice.Chain, error) {
if !signature.enabled {
return func(chain alice.Chain) alice.Chain {
return chain
@@ -199,9 +196,9 @@ func (s *engine) signatureVerifier(signature signatureSetting) (func(chain alice
}
return func(chain alice.Chain) alice.Chain {
if s.unsignedCallback != nil {
if ng.unsignedCallback != nil {
return chain.Append(handler.ContentSecurityHandler(
decrypters, signature.Expiry, signature.Strict, s.unsignedCallback))
decrypters, signature.Expiry, signature.Strict, ng.unsignedCallback))
}
return chain.Append(handler.ContentSecurityHandler(
@@ -209,8 +206,25 @@ func (s *engine) signatureVerifier(signature signatureSetting) (func(chain alice
}, nil
}
func (s *engine) use(middleware Middleware) {
s.middlewares = append(s.middlewares, middleware)
func (ng *engine) start(router httpx.Router) error {
if err := ng.bindRoutes(router); err != nil {
return err
}
if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 {
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router)
}
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
ng.conf.KeyFile, router, func(srv *http.Server) {
if ng.tlsConfig != nil {
srv.TLSConfig = ng.tlsConfig
}
})
}
func (ng *engine) use(middleware Middleware) {
ng.middlewares = append(ng.middlewares, middleware)
}
func convertMiddleware(ware Middleware) func(http.Handler) http.Handler {

View File

@@ -4,6 +4,7 @@ import (
"errors"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/conf"
@@ -143,17 +144,52 @@ Verbose: true
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(yaml), &cnf))
ng := newEngine(cnf)
ng.AddRoutes(route)
ng.addRoutes(route)
ng.use(func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
}
})
assert.NotNil(t, ng.StartWithRouter(mockedRouter{}))
assert.NotNil(t, ng.start(mockedRouter{}))
}
}
}
func TestEngine_checkedTimeout(t *testing.T) {
tests := []struct {
name string
timeout time.Duration
expect time.Duration
}{
{
name: "not set",
expect: time.Second,
},
{
name: "less",
timeout: time.Millisecond * 500,
expect: time.Millisecond * 500,
},
{
name: "equal",
timeout: time.Second,
expect: time.Second,
},
{
name: "more",
timeout: time.Millisecond * 1500,
expect: time.Millisecond * 1500,
},
}
ng := newEngine(RestConf{
Timeout: 1000,
})
for _, test := range tests {
assert.Equal(t, test.expect, ng.checkedTimeout(test.timeout))
}
}
type mockedRouter struct{}
func (m mockedRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) {

View File

@@ -16,6 +16,7 @@ import (
"github.com/tal-tech/go-zero/core/iox"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/timex"
"github.com/tal-tech/go-zero/core/utils"
"github.com/tal-tech/go-zero/rest/httpx"
@@ -23,10 +24,12 @@ import (
)
const (
limitBodyBytes = 1024
slowThreshold = time.Millisecond * 500
limitBodyBytes = 1024
defaultSlowThreshold = time.Millisecond * 500
)
var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
type loggedResponseWriter struct {
w http.ResponseWriter
r *http.Request
@@ -140,6 +143,11 @@ func DetailedLogHandler(next http.Handler) http.Handler {
})
}
// SetSlowThreshold sets the slow threshold.
func SetSlowThreshold(threshold time.Duration) {
slowThreshold.Set(threshold)
}
func dumpRequest(r *http.Request) string {
reqContent, err := httputil.DumpRequest(r, true)
if err != nil {
@@ -155,7 +163,7 @@ func logBrief(r *http.Request, code int, timer *utils.ElapsedTimer, logs *intern
logger := logx.WithContext(r.Context())
buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s - %s - %s",
r.Method, code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(), timex.ReprOfDuration(duration)))
if duration > slowThreshold {
if duration > slowThreshold.Load() {
logger.Slowf("[HTTP] %s - %d - %s - %s - %s - slowcall(%s)",
r.Method, code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(), timex.ReprOfDuration(duration))
}
@@ -191,7 +199,7 @@ func logDetails(r *http.Request, response *detailLoggedResponseWriter, timer *ut
logger := logx.WithContext(r.Context())
buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s\n=> %s\n",
r.Method, response.writer.code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r)))
if duration > slowThreshold {
if duration > defaultSlowThreshold {
logger.Slowf("[HTTP] %s - %d - %s - slowcall(%s)\n=> %s\n",
r.Method, response.writer.code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r))
}

View File

@@ -53,7 +53,7 @@ func TestLogHandlerSlow(t *testing.T) {
for _, logHandler := range handlers {
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
handler := logHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(slowThreshold + time.Millisecond*50)
time.Sleep(defaultSlowThreshold + time.Millisecond*50)
}))
resp := httptest.NewRecorder()
@@ -100,6 +100,12 @@ func TestDetailedLogHandler_Hijack(t *testing.T) {
})
}
func TestSetSlowThreshold(t *testing.T) {
assert.Equal(t, defaultSlowThreshold, slowThreshold.Load())
SetSlowThreshold(time.Second)
assert.Equal(t, time.Second, slowThreshold.Load())
}
func BenchmarkLogHandler(b *testing.B) {
b.ReportAllocs()

View File

@@ -1,27 +0,0 @@
package rest
import "net/http"
const (
allowOrigin = "Access-Control-Allow-Origin"
allOrigins = "*"
allowMethods = "Access-Control-Allow-Methods"
allowHeaders = "Access-Control-Allow-Headers"
headers = "Content-Type, Content-Length, Origin"
methods = "GET, HEAD, POST, PATCH, PUT, DELETE"
)
// CorsHandler handles cross domain OPTIONS requests.
// At most one origin can be specified, other origins are ignored if given.
func CorsHandler(origins ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if len(origins) > 0 {
w.Header().Set(allowOrigin, origins[0])
} else {
w.Header().Set(allowOrigin, allOrigins)
}
w.Header().Set(allowMethods, methods)
w.Header().Set(allowHeaders, headers)
w.WriteHeader(http.StatusNoContent)
})
}

View File

@@ -1,42 +0,0 @@
package rest
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCorsHandlerWithOrigins(t *testing.T) {
tests := []struct {
name string
origins []string
expect string
}{
{
name: "allow all origins",
expect: allOrigins,
},
{
name: "allow one origin",
origins: []string{"local"},
expect: "local",
},
{
name: "allow many origins",
origins: []string{"local", "remote"},
expect: "local",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
w := httptest.NewRecorder()
handler := CorsHandler(test.origins...)
handler.ServeHTTP(w, nil)
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
})
}
}

View File

@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/rest/pathvar"
)
func TestParseForm(t *testing.T) {
@@ -17,7 +18,7 @@ func TestParseForm(t *testing.T) {
Percent float64 `form:"percent,optional"`
}
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", nil)
r, err := http.NewRequest(http.MethodGet, "/a?name=hello&age=18&percent=3.4", nil)
assert.Nil(t, err)
assert.Nil(t, Parse(r, &v))
assert.Equal(t, "hello", v.Name)
@@ -25,11 +26,83 @@ func TestParseForm(t *testing.T) {
assert.Equal(t, 3.4, v.Percent)
}
func TestParseForm_Error(t *testing.T) {
var v struct {
Name string `form:"name"`
Age int `form:"age"`
}
r := httptest.NewRequest(http.MethodGet, "/a?name=hello;", nil)
assert.NotNil(t, ParseForm(r, &v))
}
func TestParseHeader(t *testing.T) {
m := ParseHeader("key=value;")
assert.EqualValues(t, map[string]string{
"key": "value",
}, m)
tests := []struct {
name string
value string
expect map[string]string
}{
{
name: "empty",
value: "",
expect: map[string]string{},
},
{
name: "regular",
value: "key=value",
expect: map[string]string{"key": "value"},
},
{
name: "next empty",
value: "key=value;",
expect: map[string]string{"key": "value"},
},
{
name: "regular",
value: "key=value;foo",
expect: map[string]string{"key": "value"},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
m := ParseHeader(test.value)
assert.EqualValues(t, test.expect, m)
})
}
}
func TestParsePath(t *testing.T) {
var v struct {
Name string `path:"name"`
Age int `path:"age"`
}
r := httptest.NewRequest(http.MethodGet, "/", nil)
r = pathvar.WithVars(r, map[string]string{
"name": "foo",
"age": "18",
})
err := Parse(r, &v)
assert.Nil(t, err)
assert.Equal(t, "foo", v.Name)
assert.Equal(t, 18, v.Age)
}
func TestParsePath_Error(t *testing.T) {
var v struct {
Name string `path:"name"`
Age int `path:"age"`
}
r := httptest.NewRequest(http.MethodGet, "/", nil)
r = pathvar.WithVars(r, map[string]string{
"name": "foo",
})
assert.NotNil(t, Parse(r, &v))
}
func TestParseFormOutOfRange(t *testing.T) {
@@ -42,23 +115,23 @@ func TestParseFormOutOfRange(t *testing.T) {
pass bool
}{
{
url: "http://hello.com/a?age=5",
url: "/a?age=5",
pass: false,
},
{
url: "http://hello.com/a?age=10",
url: "/a?age=10",
pass: true,
},
{
url: "http://hello.com/a?age=15",
url: "/a?age=15",
pass: true,
},
{
url: "http://hello.com/a?age=20",
url: "/a?age=20",
pass: false,
},
{
url: "http://hello.com/a?age=28",
url: "/a?age=28",
pass: false,
},
}
@@ -92,7 +165,7 @@ Content-Disposition: form-data; name="age"
18
----------------------------220477612388154780019383--`, "\n", "\r\n", -1)
r := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
assert.Nil(t, Parse(r, &v))
@@ -116,7 +189,7 @@ Content-Disposition: form-data; name="age"
18
----------------------------22047761238815478001938--`, "\n", "\r\n", -1)
r := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
assert.NotNil(t, Parse(r, &v))
@@ -129,7 +202,7 @@ func TestParseJsonBody(t *testing.T) {
}
body := `{"name":"kevin", "age": 18}`
r := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
r.Header.Set(ContentType, ApplicationJson)
assert.Nil(t, Parse(r, &v))
@@ -143,7 +216,7 @@ func TestParseRequired(t *testing.T) {
Percent float64 `form:"percent"`
}{}
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello", nil)
r, err := http.NewRequest(http.MethodGet, "/a?name=hello", nil)
assert.Nil(t, err)
assert.NotNil(t, Parse(r, &v))
}
@@ -153,11 +226,57 @@ func TestParseOptions(t *testing.T) {
Position int8 `form:"pos,options=1|2"`
}{}
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?pos=4", nil)
r, err := http.NewRequest(http.MethodGet, "/a?pos=4", nil)
assert.Nil(t, err)
assert.NotNil(t, Parse(r, &v))
}
func TestParseHeaders(t *testing.T) {
type AnonymousStruct struct {
XRealIP string `header:"x-real-ip"`
Accept string `header:"Accept,optional"`
}
v := struct {
Name string `header:"name,optional"`
Percent string `header:"percent"`
Addrs []string `header:"addrs"`
XForwardedFor string `header:"X-Forwarded-For,optional"`
AnonymousStruct
}{}
request, err := http.NewRequest("POST", "/", nil)
if err != nil {
t.Fatal(err)
}
request.Header.Set("name", "chenquan")
request.Header.Set("percent", "1")
request.Header.Add("addrs", "addr1")
request.Header.Add("addrs", "addr2")
request.Header.Add("X-Forwarded-For", "10.0.10.11")
request.Header.Add("x-real-ip", "10.0.11.10")
request.Header.Add("Accept", "application/json")
err = ParseHeaders(request, &v)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "chenquan", v.Name)
assert.Equal(t, "1", v.Percent)
assert.Equal(t, []string{"addr1", "addr2"}, v.Addrs)
assert.Equal(t, "10.0.10.11", v.XForwardedFor)
assert.Equal(t, "10.0.11.10", v.XRealIP)
assert.Equal(t, "application/json", v.Accept)
}
func TestParseHeaders_Error(t *testing.T) {
v := struct {
Name string `header:"name"`
Age int `header:"age"`
}{}
r := httptest.NewRequest("POST", "/", nil)
r.Header.Set("name", "foo")
assert.NotNil(t, Parse(r, &v))
}
func BenchmarkParseRaw(b *testing.B) {
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", nil)
if err != nil {
@@ -201,38 +320,3 @@ func BenchmarkParseAuto(b *testing.B) {
}
}
}
func TestParseHeaders(t *testing.T) {
type AnonymousStruct struct {
XRealIP string `header:"x-real-ip"`
Accept string `header:"Accept,optional"`
}
v := struct {
Name string `header:"name,optional"`
Percent string `header:"percent"`
Addrs []string `header:"addrs"`
XForwardedFor string `header:"X-Forwarded-For,optional"`
AnonymousStruct
}{}
request, err := http.NewRequest("POST", "http://hello.com/", nil)
if err != nil {
t.Fatal(err)
}
request.Header.Set("name", "chenquan")
request.Header.Set("percent", "1")
request.Header.Add("addrs", "addr1")
request.Header.Add("addrs", "addr2")
request.Header.Add("X-Forwarded-For", "10.0.10.11")
request.Header.Add("x-real-ip", "10.0.11.10")
request.Header.Add("Accept", "application/json")
err = ParseHeaders(request, &v)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "chenquan", v.Name)
assert.Equal(t, "1", v.Percent)
assert.Equal(t, []string{"addr1", "addr2"}, v.Addrs)
assert.Equal(t, "10.0.10.11", v.XForwardedFor)
assert.Equal(t, "10.0.11.10", v.XRealIP)
assert.Equal(t, "application/json", v.Accept)
}

View File

@@ -24,7 +24,7 @@ func Error(w http.ResponseWriter, err error) {
return
}
code, body := errorHandler(err)
code, body := handler(err)
if body == nil {
w.WriteHeader(code)
return

View File

@@ -10,5 +10,6 @@ func GetRemoteAddr(r *http.Request) string {
if len(v) > 0 {
return v
}
return r.RemoteAddr
}

View File

@@ -16,3 +16,10 @@ func TestGetRemoteAddr(t *testing.T) {
r.Header.Set(xForwardedFor, host)
assert.Equal(t, host, GetRemoteAddr(r))
}
func TestGetRemoteAddrNoHeader(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "/", strings.NewReader(""))
assert.Nil(t, err)
assert.True(t, len(GetRemoteAddr(r)) == 0)
}

View File

@@ -0,0 +1,100 @@
package cors
import "net/http"
const (
allowOrigin = "Access-Control-Allow-Origin"
allOrigins = "*"
allowMethods = "Access-Control-Allow-Methods"
allowHeaders = "Access-Control-Allow-Headers"
allowCredentials = "Access-Control-Allow-Credentials"
exposeHeaders = "Access-Control-Expose-Headers"
requestMethod = "Access-Control-Request-Method"
requestHeaders = "Access-Control-Request-Headers"
allowHeadersVal = "Content-Type, Origin, X-CSRF-Token, Authorization, AccessToken, Token, Range"
exposeHeadersVal = "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers"
methods = "GET, HEAD, POST, PATCH, PUT, DELETE"
allowTrue = "true"
maxAgeHeader = "Access-Control-Max-Age"
maxAgeHeaderVal = "86400"
varyHeader = "Vary"
originHeader = "Origin"
)
// NotAllowedHandler handles cross domain not allowed requests.
// At most one origin can be specified, other origins are ignored if given, default to be *.
func NotAllowedHandler(origins ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
checkAndSetHeaders(w, r, origins)
if r.Method != http.MethodOptions {
w.WriteHeader(http.StatusNotFound)
} else {
w.WriteHeader(http.StatusNoContent)
}
})
}
// Middleware returns a middleware that adds CORS headers to the response.
func Middleware(origins ...string) func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
checkAndSetHeaders(w, r, origins)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
} else {
next(w, r)
}
}
}
}
func checkAndSetHeaders(w http.ResponseWriter, r *http.Request, origins []string) {
setVaryHeaders(w, r)
if len(origins) == 0 {
setHeader(w, allOrigins)
return
}
origin := r.Header.Get(originHeader)
if isOriginAllowed(origins, origin) {
setHeader(w, origin)
}
}
func isOriginAllowed(allows []string, origin string) bool {
for _, o := range allows {
if o == allOrigins {
return true
}
if o == origin {
return true
}
}
return false
}
func setHeader(w http.ResponseWriter, origin string) {
header := w.Header()
header.Set(allowOrigin, origin)
header.Set(allowMethods, methods)
header.Set(allowHeaders, allowHeadersVal)
header.Set(exposeHeaders, exposeHeadersVal)
if origin != allOrigins {
header.Set(allowCredentials, allowTrue)
}
header.Set(maxAgeHeader, maxAgeHeaderVal)
}
func setVaryHeaders(w http.ResponseWriter, r *http.Request) {
header := w.Header()
header.Add(varyHeader, originHeader)
if r.Method == http.MethodOptions {
header.Add(varyHeader, requestMethod)
header.Add(varyHeader, requestHeaders)
}
}

View File

@@ -0,0 +1,97 @@
package cors
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCorsHandlerWithOrigins(t *testing.T) {
tests := []struct {
name string
origins []string
reqOrigin string
expect string
}{
{
name: "allow all origins",
expect: allOrigins,
},
{
name: "allow one origin",
origins: []string{"http://local"},
reqOrigin: "http://local",
expect: "http://local",
},
{
name: "allow many origins",
origins: []string{"http://local", "http://remote"},
reqOrigin: "http://local",
expect: "http://local",
},
{
name: "allow all origins",
reqOrigin: "http://local",
expect: "*",
},
{
name: "allow many origins with all mark",
origins: []string{"http://local", "http://remote", "*"},
reqOrigin: "http://another",
expect: "http://another",
},
{
name: "not allow origin",
origins: []string{"http://local", "http://remote"},
reqOrigin: "http://another",
},
}
methods := []string{
http.MethodOptions,
http.MethodGet,
http.MethodPost,
}
for _, test := range tests {
for _, method := range methods {
test := test
t.Run(test.name+"-handler", func(t *testing.T) {
r := httptest.NewRequest(method, "http://localhost", nil)
r.Header.Set(originHeader, test.reqOrigin)
w := httptest.NewRecorder()
handler := NotAllowedHandler(test.origins...)
handler.ServeHTTP(w, r)
if method == http.MethodOptions {
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
} else {
assert.Equal(t, http.StatusNotFound, w.Result().StatusCode)
}
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
})
}
}
for _, test := range tests {
for _, method := range methods {
test := test
t.Run(test.name+"-middleware", func(t *testing.T) {
r := httptest.NewRequest(method, "http://localhost", nil)
r.Header.Set(originHeader, test.reqOrigin)
w := httptest.NewRecorder()
handler := Middleware(test.origins...)(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
handler.ServeHTTP(w, r)
if method == http.MethodOptions {
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
} else {
assert.Equal(t, http.StatusOK, w.Result().StatusCode)
}
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
})
}
}
}

View File

@@ -5,31 +5,43 @@ import (
"fmt"
"net/http"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/proc"
)
// StartOption defines the method to customize http.Server.
type StartOption func(srv *http.Server)
// StartHttp starts a http server.
func StartHttp(host string, port int, handler http.Handler) error {
func StartHttp(host string, port int, handler http.Handler, opts ...StartOption) error {
return start(host, port, handler, func(srv *http.Server) error {
return srv.ListenAndServe()
})
}, opts...)
}
// StartHttps starts a https server.
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler) error {
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler,
opts ...StartOption) error {
return start(host, port, handler, func(srv *http.Server) error {
// certFile and keyFile are set in buildHttpsServer
return srv.ListenAndServeTLS(certFile, keyFile)
})
}, opts...)
}
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error) (err error) {
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error,
opts ...StartOption) (err error) {
server := &http.Server{
Addr: fmt.Sprintf("%s:%d", host, port),
Handler: handler,
}
for _, opt := range opts {
opt(server)
}
waitForCalled := proc.AddWrapUpListener(func() {
server.Shutdown(context.Background())
if e := server.Shutdown(context.Background()); err != nil {
logx.Error(e)
}
})
defer func() {
if err == http.ErrServerClosed {

View File

@@ -105,19 +105,22 @@ func TestPatRouter(t *testing.T) {
t.Run(test.method+":"+test.path, func(t *testing.T) {
routed := false
router := NewRouter()
err := router.Handle(test.method, "/a/:b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
routed = true
assert.Equal(t, 1, len(pathvar.Vars(r)))
}))
err := router.Handle(test.method, "/a/:b", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
routed = true
assert.Equal(t, 1, len(pathvar.Vars(r)))
}))
assert.Nil(t, err)
err = router.Handle(test.method, "/a/b/c", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
routed = true
assert.Nil(t, pathvar.Vars(r))
}))
err = router.Handle(test.method, "/a/b/c", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
routed = true
assert.Nil(t, pathvar.Vars(r))
}))
assert.Nil(t, err)
err = router.Handle(test.method, "/b/c", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
routed = true
}))
err = router.Handle(test.method, "/b/c", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
routed = true
}))
assert.Nil(t, err)
w := new(mockedResponseWriter)

View File

@@ -1,27 +1,27 @@
package rest
import (
"crypto/tls"
"log"
"net/http"
"path"
"time"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/rest/handler"
"github.com/tal-tech/go-zero/rest/httpx"
"github.com/tal-tech/go-zero/rest/internal/cors"
"github.com/tal-tech/go-zero/rest/router"
)
type (
runOptions struct {
start func(*engine) error
}
// RunOption defines the method to customize a Server.
RunOption func(*Server)
// A Server is a http server.
Server struct {
ngin *engine
opts runOptions
ngin *engine
router httpx.Router
}
)
@@ -45,12 +45,8 @@ func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
}
server := &Server{
ngin: newEngine(c),
opts: runOptions{
start: func(srv *engine) error {
return srv.Start()
},
},
ngin: newEngine(c),
router: router.NewRouter(),
}
for _, opt := range opts {
@@ -68,7 +64,7 @@ func (s *Server) AddRoutes(rs []Route, opts ...RouteOption) {
for _, opt := range opts {
opt(&r)
}
s.ngin.AddRoutes(r)
s.ngin.addRoutes(r)
}
// AddRoute adds given route into the Server.
@@ -80,7 +76,7 @@ func (s *Server) AddRoute(r Route, opts ...RouteOption) {
// Graceful shutdown is enabled by default.
// Use proc.SetTimeToForceQuit to customize the graceful shutdown period.
func (s *Server) Start() {
handleError(s.opts.start(s.ngin))
handleError(s.ngin.start(s.router))
}
// Stop stops the Server.
@@ -100,6 +96,14 @@ func ToMiddleware(handler func(next http.Handler) http.Handler) Middleware {
}
}
// WithCors returns a func to enable CORS for given origin, or default to all origins (*).
func WithCors(origin ...string) RunOption {
return func(server *Server) {
server.router.SetNotAllowedHandler(cors.NotAllowedHandler(origin...))
server.Use(cors.Middleware(origin...))
}
}
// WithJwt returns a func to enable jwt authentication in given route.
func WithJwt(secret string) RouteOption {
return func(r *featuredRoutes) {
@@ -148,16 +152,32 @@ func WithMiddleware(middleware Middleware, rs ...Route) []Route {
// WithNotFoundHandler returns a RunOption with not found handler set to given handler.
func WithNotFoundHandler(handler http.Handler) RunOption {
rt := router.NewRouter()
rt.SetNotFoundHandler(handler)
return WithRouter(rt)
return func(server *Server) {
server.router.SetNotFoundHandler(handler)
}
}
// WithNotAllowedHandler returns a RunOption with not allowed handler set to given handler.
func WithNotAllowedHandler(handler http.Handler) RunOption {
rt := router.NewRouter()
rt.SetNotAllowedHandler(handler)
return WithRouter(rt)
return func(server *Server) {
server.router.SetNotAllowedHandler(handler)
}
}
// WithPrefix adds group as a prefix to the route paths.
func WithPrefix(group string) RouteOption {
return func(r *featuredRoutes) {
var routes []Route
for _, rt := range r.routes {
p := path.Join(group, rt.Path)
routes = append(routes, Route{
Method: rt.Method,
Path: p,
Handler: rt.Handler,
})
}
r.routes = routes
}
}
// WithPriority returns a RunOption with priority.
@@ -170,9 +190,7 @@ func WithPriority() RouteOption {
// WithRouter returns a RunOption that make server run with given router.
func WithRouter(router httpx.Router) RunOption {
return func(server *Server) {
server.opts.start = func(srv *engine) error {
return srv.StartWithRouter(router)
}
server.router = router
}
}
@@ -186,17 +204,31 @@ func WithSignature(signature SignatureConf) RouteOption {
}
}
// WithTimeout returns a RouteOption to set timeout with given value.
func WithTimeout(timeout time.Duration) RouteOption {
return func(r *featuredRoutes) {
r.timeout = timeout
}
}
// WithTLSConfig returns a RunOption that with given tls config.
func WithTLSConfig(cfg *tls.Config) RunOption {
return func(srv *Server) {
srv.ngin.setTlsConfig(cfg)
}
}
// WithUnauthorizedCallback returns a RunOption that with given unauthorized callback set.
func WithUnauthorizedCallback(callback handler.UnauthorizedCallback) RunOption {
return func(engine *Server) {
engine.ngin.SetUnauthorizedCallback(callback)
return func(srv *Server) {
srv.ngin.setUnauthorizedCallback(callback)
}
}
// WithUnsignedCallback returns a RunOption that with given unsigned callback set.
func WithUnsignedCallback(callback handler.UnsignedCallback) RunOption {
return func(engine *Server) {
engine.ngin.SetUnsignedCallback(callback)
return func(srv *Server) {
srv.ngin.setUnsignedCallback(callback)
}
}

View File

@@ -1,11 +1,13 @@
package rest
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/conf"
@@ -20,11 +22,6 @@ Port: 54321
`
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
failStart := func(server *Server) {
server.opts.start = func(e *engine) error {
return http.ErrServerClosed
}
}
tests := []struct {
c RestConf
@@ -33,38 +30,40 @@ Port: 54321
}{
{
c: RestConf{},
opts: []RunOption{failStart},
opts: []RunOption{WithRouter(mockedRouter{}), WithCors()},
fail: true,
},
{
c: cnf,
opts: []RunOption{failStart},
opts: []RunOption{WithRouter(mockedRouter{})},
},
{
c: cnf,
opts: []RunOption{WithNotAllowedHandler(nil), failStart},
opts: []RunOption{WithRouter(mockedRouter{}), WithNotAllowedHandler(nil)},
},
{
c: cnf,
opts: []RunOption{WithNotFoundHandler(nil), failStart},
opts: []RunOption{WithNotFoundHandler(nil), WithRouter(mockedRouter{})},
},
{
c: cnf,
opts: []RunOption{WithUnauthorizedCallback(nil), failStart},
opts: []RunOption{WithUnauthorizedCallback(nil), WithRouter(mockedRouter{})},
},
{
c: cnf,
opts: []RunOption{WithUnsignedCallback(nil), failStart},
opts: []RunOption{WithUnsignedCallback(nil), WithRouter(mockedRouter{})},
},
}
for _, test := range tests {
srv, err := NewServer(test.c, test.opts...)
var srv *Server
var err error
if test.fail {
_, err = NewServer(test.c, test.opts...)
assert.NotNil(t, err)
}
if err != nil {
continue
} else {
srv = MustNewServer(test.c, test.opts...)
}
srv.Use(ToMiddleware(func(next http.Handler) http.Handler {
@@ -78,8 +77,21 @@ Port: 54321
Handler: nil,
}, WithJwt("thesecret"), WithSignature(SignatureConf{}),
WithJwtTransition("preivous", "thenewone"))
srv.Start()
srv.Stop()
func() {
defer func() {
p := recover()
switch v := p.(type) {
case error:
assert.Equal(t, "foo", v.Error())
default:
t.Fail()
}
}()
srv.Start()
srv.Stop()
}()
}
}
@@ -178,6 +190,9 @@ func TestMultiMiddlewares(t *testing.T) {
next.ServeHTTP(w, r)
}
},
ToMiddleware(func(next http.Handler) http.Handler {
return next
}),
}, Route{
Method: http.MethodGet,
Path: "/first/:name/:year",
@@ -212,8 +227,86 @@ func TestMultiMiddlewares(t *testing.T) {
}, m)
}
func TestWithPrefix(t *testing.T) {
fr := featuredRoutes{
routes: []Route{
{
Path: "/hello",
},
{
Path: "/world",
},
},
}
WithPrefix("/api")(&fr)
var vals []string
for _, r := range fr.routes {
vals = append(vals, r.Path)
}
assert.EqualValues(t, []string{"/api/hello", "/api/world"}, vals)
}
func TestWithPriority(t *testing.T) {
var fr featuredRoutes
WithPriority()(&fr)
assert.True(t, fr.priority)
}
func TestWithTimeout(t *testing.T) {
var fr featuredRoutes
WithTimeout(time.Hour)(&fr)
assert.Equal(t, time.Hour, fr.timeout)
}
func TestWithTLSConfig(t *testing.T) {
const configYaml = `
Name: foo
Port: 54321
`
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
testConfig := &tls.Config{
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}
testCases := []struct {
c RestConf
opts []RunOption
res *tls.Config
}{
{
c: cnf,
opts: []RunOption{WithTLSConfig(testConfig)},
res: testConfig,
},
{
c: cnf,
opts: []RunOption{WithUnsignedCallback(nil)},
res: nil,
},
}
for _, testCase := range testCases {
srv, err := NewServer(testCase.c, testCase.opts...)
assert.Nil(t, err)
assert.Equal(t, srv.ngin.tlsConfig, testCase.res)
}
}
func TestWithCors(t *testing.T) {
const configYaml = `
Name: foo
Port: 54321
`
var cnf RestConf
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
rt := router.NewRouter()
srv, err := NewServer(cnf, WithRouter(rt))
assert.Nil(t, err)
opt := WithCors("local")
opt(srv)
}

View File

@@ -1,6 +1,9 @@
package rest
import "net/http"
import (
"net/http"
"time"
)
type (
// Middleware defines the middleware method.
@@ -28,6 +31,7 @@ type (
}
featuredRoutes struct {
timeout time.Duration
priority bool
jwt jwtSetting
signature signatureSetting

View File

@@ -25,6 +25,7 @@ func DartCommand(c *cli.Context) error {
return err
}
api.Service = api.Service.JoinPrefix()
if !strings.HasSuffix(dir, "/") {
dir = dir + "/"
}

View File

@@ -42,14 +42,15 @@ func DocCommand(c *cli.Context) error {
return err
}
for _, path := range files {
api, err := parser.Parse(path)
for _, p := range files {
api, err := parser.Parse(p)
if err != nil {
return fmt.Errorf("parse file: %s, err: %s", path, err.Error())
return fmt.Errorf("parse file: %s, err: %s", p, err.Error())
}
err = genDoc(api, filepath.Dir(filepath.Join(outputDir, path[len(dir):])),
strings.Replace(path[len(filepath.Dir(path)):], ".api", ".md", 1))
api.Service = api.Service.JoinPrefix()
err = genDoc(api, filepath.Dir(filepath.Join(outputDir, p[len(dir):])),
strings.Replace(p[len(filepath.Dir(p)):], ".api", ".md", 1))
if err != nil {
return err
}

View File

@@ -27,13 +27,13 @@ import (
{{.importPackages}}
)
func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
{{.routesAdditions}}
}
`
routesAdditionTemplate = `
engine.AddRoutes(
{{.routes}} {{.jwt}}{{.signature}}
server.AddRoutes(
{{.routes}} {{.jwt}}{{.signature}} {{.prefix}}
)
`
)
@@ -54,6 +54,7 @@ type (
signatureEnabled bool
authName string
middlewares []string
prefix string
}
route struct {
method string
@@ -87,10 +88,14 @@ func genRoutes(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error
if g.jwtEnabled {
jwt = fmt.Sprintf("\n rest.WithJwt(serverCtx.Config.%s.AccessSecret),", g.authName)
}
var signature string
var signature, prefix string
if g.signatureEnabled {
signature = "\n rest.WithSignature(serverCtx.Config.Signature),"
}
if len(g.prefix) > 0 {
prefix = fmt.Sprintf(`
rest.WithPrefix("%s"),`, g.prefix)
}
var routes string
if len(g.middlewares) > 0 {
@@ -111,6 +116,7 @@ func genRoutes(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error
"routes": routes,
"jwt": jwt,
"signature": signature,
"prefix": prefix,
}); err != nil {
return err
}
@@ -200,6 +206,13 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
groupedRoutes.middlewares = append(groupedRoutes.middlewares,
strings.Split(middleware, ",")...)
}
prefix := g.GetAnnotation(spec.RoutePrefixKey)
prefix = strings.ReplaceAll(prefix, `"`, "")
prefix = strings.TrimSpace(prefix)
if len(prefix) > 0 {
prefix = path.Join("/", prefix)
groupedRoutes.prefix = prefix
}
routes = append(routes, groupedRoutes)
}

View File

@@ -28,6 +28,7 @@ func JavaCommand(c *cli.Context) error {
return err
}
api.Service = api.Service.JoinPrefix()
packetName := strings.TrimSuffix(api.Service.Name, "-api")
logx.Must(util.MkdirIfNotExist(dir))
logx.Must(genPacket(dir, packetName, api))

View File

@@ -27,6 +27,7 @@ func KtCommand(c *cli.Context) error {
return e
}
api.Service = api.Service.JoinPrefix()
e = genBase(dir, pkg, api)
if e != nil {
return e

View File

@@ -24,7 +24,7 @@ type Response {
service {{.name}}-api {
@handler {{.handler}}Handler
get /from/:name(Request) returns (Response);
get /from/:name(Request) returns (Response)
}
`

View File

@@ -212,24 +212,40 @@ func (v *ApiVisitor) VisitRoute(ctx *api.RouteContext) interface{} {
// VisitBody implements from api.BaseApiParserVisitor
func (v *ApiVisitor) VisitBody(ctx *api.BodyContext) interface{} {
if ctx.ID() == nil {
if v.debug {
msg := fmt.Sprintf(
`%s line %d: expr "()" is deprecated, if there has no request body, please omit it`,
v.prefix,
ctx.GetStart().GetLine(),
)
v.log.Warning(msg)
}
return nil
}
idRxpr := v.newExprWithTerminalNode(ctx.ID())
if api.IsGolangKeyWord(idRxpr.Text()) {
v.panic(idRxpr, fmt.Sprintf("expecting 'ID', but found golang keyword '%s'", idRxpr.Text()))
idExpr := v.newExprWithTerminalNode(ctx.ID())
if api.IsGolangKeyWord(idExpr.Text()) {
v.panic(idExpr, fmt.Sprintf("expecting 'ID', but found golang keyword '%s'", idExpr.Text()))
}
return &Body{
Lp: v.newExprWithToken(ctx.GetLp()),
Rp: v.newExprWithToken(ctx.GetRp()),
Name: &Literal{Literal: idRxpr},
Name: &Literal{Literal: idExpr},
}
}
// VisitReplybody implements from api.BaseApiParserVisitor
func (v *ApiVisitor) VisitReplybody(ctx *api.ReplybodyContext) interface{} {
if ctx.DataType() == nil {
if v.debug {
msg := fmt.Sprintf(
`%s line %d: expr "returns ()" or "()" is deprecated, if there has no response body, please omit it`,
v.prefix,
ctx.GetStart().GetLine(),
)
v.log.Warning(msg)
}
return nil
}

View File

@@ -17,7 +17,7 @@ type parser struct {
// Parse parses the api file
func Parse(filename string) (*spec.ApiSpec, error) {
astParser := ast.NewParser(ast.WithParserPrefix(filepath.Base(filename)))
astParser := ast.NewParser(ast.WithParserPrefix(filepath.Base(filename)), ast.WithParserDebug())
ast, err := astParser.Parse(filename)
if err != nil {
return nil, err

View File

@@ -2,6 +2,7 @@ package spec
import (
"errors"
"path"
"strings"
"github.com/tal-tech/go-zero/core/stringx"
@@ -17,6 +18,23 @@ const (
var definedKeys = []string{bodyTagKey, formTagKey, pathTagKey}
func (s Service) JoinPrefix() Service {
var groups []Group
for _, g := range s.Groups {
prefix := strings.TrimSpace(g.GetAnnotation(RoutePrefixKey))
prefix = strings.ReplaceAll(prefix, `"`, "")
var routes []Route
for _, r := range g.Routes {
r.Path = path.Join("/", prefix, r.Path)
routes = append(routes, r)
}
g.Routes = routes
groups = append(groups, g)
}
s.Groups = groups
return s
}
// Routes returns all routes in api service
func (s Service) Routes() []Route {
var result []Route

View File

@@ -1,5 +1,7 @@
package spec
const RoutePrefixKey = "prefix"
type (
// Doc describes document
Doc []string

View File

@@ -32,6 +32,7 @@ func TsCommand(c *cli.Context) error {
return err
}
api.Service = api.Service.JoinPrefix()
logx.Must(util.MkdirIfNotExist(dir))
logx.Must(genHandler(dir, webAPI, caller, api, unwrapAPI))
logx.Must(genComponents(dir, api))

View File

@@ -30,6 +30,7 @@ type Docker struct {
HasPort bool
Port int
Argument string
Version string
}
// DockerCommand provides the entry for goctl docker
@@ -42,6 +43,11 @@ func DockerCommand(c *cli.Context) (err error) {
goFile := c.String("go")
home := c.String("home")
version := c.String("version")
if len(version) > 0 {
version = version + "-"
}
if len(home) > 0 {
util.RegisterGoctlHome(home)
@@ -57,7 +63,7 @@ func DockerCommand(c *cli.Context) (err error) {
port := c.Int("port")
if _, err := os.Stat(etcDir); os.IsNotExist(err) {
return generateDockerfile(goFile, port)
return generateDockerfile(goFile, port, version)
}
cfg, err := findConfig(goFile, etcDir)
@@ -65,13 +71,13 @@ func DockerCommand(c *cli.Context) (err error) {
return err
}
if err := generateDockerfile(goFile, port, "-f", "etc/"+cfg); err != nil {
if err := generateDockerfile(goFile, port, version, "-f", "etc/"+cfg); err != nil {
return err
}
projDir, ok := util.FindProjectPath(goFile)
if ok {
fmt.Printf("Hint: run \"docker build ...\" command in dir %q\n", projDir)
fmt.Printf("Hint: run \"docker build ...\" command in dir:\n %s\n", projDir)
}
return nil
@@ -106,7 +112,7 @@ func findConfig(file, dir string) (string, error) {
return files[0], nil
}
func generateDockerfile(goFile string, port int, args ...string) error {
func generateDockerfile(goFile string, port int, version string, args ...string) error {
projPath, err := getFilePath(filepath.Dir(goFile))
if err != nil {
return err
@@ -114,11 +120,6 @@ func generateDockerfile(goFile string, port int, args ...string) error {
if len(projPath) == 0 {
projPath = "."
} else {
pos := strings.IndexByte(projPath, os.PathSeparator)
if pos >= 0 {
projPath = projPath[pos+1:]
}
}
out, err := util.CreateIfNotExist(dockerfileName)
@@ -147,6 +148,7 @@ func generateDockerfile(goFile string, port int, args ...string) error {
HasPort: port > 0,
Port: port,
Argument: builder.String(),
Version: version,
})
}
@@ -162,6 +164,12 @@ func getFilePath(file string) (string, error) {
if err != nil {
return "", errors.New("no go.mod found, or not in GOPATH")
}
// ignore project root directory for GOPATH mode
pos := strings.IndexByte(projPath, os.PathSeparator)
if pos >= 0 {
projPath = projPath[pos+1:]
}
}
return projPath, nil

View File

@@ -8,7 +8,7 @@ import (
const (
category = "docker"
dockerTemplateFile = "docker.tpl"
dockerTemplate = `FROM golang:alpine AS builder
dockerTemplate = `FROM golang:{{.Version}}alpine AS builder
LABEL stage=gobuilder

View File

@@ -3,14 +3,11 @@ package main
import (
"fmt"
"os"
"path/filepath"
"runtime"
"syscall"
"github.com/logrusorgru/aurora"
"github.com/tal-tech/go-zero/core/load"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/tools/goctl/api/apigen"
"github.com/tal-tech/go-zero/tools/goctl/api/dartgen"
"github.com/tal-tech/go-zero/tools/goctl/api/docgen"
@@ -31,12 +28,11 @@ import (
rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/cli"
"github.com/tal-tech/go-zero/tools/goctl/tpl"
"github.com/tal-tech/go-zero/tools/goctl/upgrade"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
"github.com/tal-tech/go-zero/tools/goctl/util/env"
"github.com/urfave/cli"
pluginCtl "github.com/zeromicro/protobuf/protoc-gen-go"
)
const codeFailure = 1
var commands = []cli.Command{
{
Name: "upgrade",
@@ -256,6 +252,10 @@ var commands = []cli.Command{
Name: "home",
Usage: "the goctl home path of the template",
},
cli.StringFlag{
Name: "version",
Usage: "the goctl builder golang image version",
},
},
Action: docker.DockerCommand,
},
@@ -646,51 +646,15 @@ var commands = []cli.Command{
func main() {
logx.Disable()
load.Disable()
stat.DisableLog()
args := os.Args
pluginName := filepath.Base(args[0])
if pluginName == protocGenGoctl {
pluginCtl.Generate()
return
}
app := cli.NewApp()
app.Usage = "a cli tool to generate code"
app.Version = fmt.Sprintf("%s %s/%s", version.BuildVersion, runtime.GOOS, runtime.GOARCH)
app.Commands = commands
// cli already print error messages
if err := app.Run(os.Args); err != nil {
fmt.Println(aurora.Red(errorx.Wrap(err).Error()))
os.Exit(codeFailure)
}
}
func init() {
err := linkProtocGenGoctl()
if err != nil {
console.Error("%+v", err)
}
}
const protocGenGoctl = "protoc-gen-goctl"
func linkProtocGenGoctl() error {
path, err := env.LookPath("goctl")
if err != nil {
return err
}
dir := filepath.Dir(path)
ext := filepath.Ext(path)
target := filepath.Join(dir, protocGenGoctl)
if len(ext) > 0 {
target = target + ext
}
err = syscall.Unlink(target)
if err != nil && !os.IsNotExist(err) {
return err
}
return os.Symlink(path, target)
}

View File

@@ -6,7 +6,7 @@ import (
)
// BuildVersion is the version of goctl.
const BuildVersion = "1.2.2"
const BuildVersion = "1.2.3"
// GetGoctlVersion returns BuildVersion
func GetGoctlVersion() string {

View File

@@ -1,136 +1,20 @@
package builderx
import (
"fmt"
"reflect"
"strings"
"github.com/go-xorm/builder"
"github.com/tal-tech/go-zero/core/stores/builder"
)
const dbTag = "db"
// NewEq wraps builder.Eq.
func NewEq(in interface{}) builder.Eq {
return builder.Eq(ToMap(in))
}
// NewGt wraps builder.Gt.
func NewGt(in interface{}) builder.Gt {
return builder.Gt(ToMap(in))
}
// ToMap converts interface into map.
func ToMap(in interface{}) map[string]interface{} {
out := make(map[string]interface{})
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
// set key of map to value in struct field
val := v.Field(i)
zero := reflect.Zero(val.Type()).Interface()
current := val.Interface()
if reflect.DeepEqual(current, zero) {
continue
}
out[tagv] = current
}
}
return out
}
// FieldNames returns field names from given in.
// deprecated: use RawFieldNames instead automatically while model generating after goctl version v1.1.0
// Deprecated: Use github.com/tal-tech/go-zero/core/stores/builder.RawFieldNames instead.
func FieldNames(in interface{}) []string {
out := make([]string, 0)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
out = append(out, tagv)
} else {
out = append(out, fi.Name)
}
}
return out
return builder.RawFieldNames(in)
}
// RawFieldNames converts golang struct field into slice string.
// Deprecated: Use github.com/tal-tech/go-zero/core/stores/builder.RawFieldNames instead.
func RawFieldNames(in interface{}, postgresSql ...bool) []string {
out := make([]string, 0)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
var pg bool
if len(postgresSql) > 0 {
pg = postgresSql[0]
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
if pg {
out = append(out, tagv)
} else {
out = append(out, fmt.Sprintf("`%s`", tagv))
}
} else {
if pg {
out = append(out, fi.Name)
} else {
out = append(out, fmt.Sprintf("`%s`", fi.Name))
}
}
}
return out
return builder.RawFieldNames(in, postgresSql...)
}
// PostgreSqlJoin concatenates the given elements into a string.
// Deprecated: Use github.com/tal-tech/go-zero/core/stores/builderx.PostgreSqlJoin instead.
func PostgreSqlJoin(elems []string) string {
b := new(strings.Builder)
for index, e := range elems {
b.WriteString(fmt.Sprintf("%s = $%d, ", e, index+2))
}
if b.Len() == 0 {
return b.String()
}
return b.String()[0 : b.Len()-2]
return builder.PostgreSqlJoin(elems)
}

View File

@@ -1,125 +0,0 @@
package builderx
import (
"fmt"
"testing"
"github.com/go-xorm/builder"
"github.com/stretchr/testify/assert"
)
type mockedUser struct {
// 自增id
ID string `db:"id" json:"id,omitempty"`
// 姓名
UserName string `db:"user_name" json:"userName,omitempty"`
// 1男,2女
Sex int `db:"sex" json:"sex,omitempty"`
UUID string `db:"uuid" uuid:"uuid,omitempty"`
Age int `db:"age" json:"age"`
}
var (
userFieldsWithRawStringQuote = RawFieldNames(mockedUser{})
userFieldsWithoutRawStringQuote = FieldNames(mockedUser{})
)
func TestFieldNames(t *testing.T) {
t.Run("old", func(t *testing.T) {
var u mockedUser
out := FieldNames(&u)
expected := []string{"id", "user_name", "sex", "uuid", "age"}
assert.Equal(t, expected, out)
})
t.Run("new", func(t *testing.T) {
var u mockedUser
out := RawFieldNames(&u)
expected := []string{"`id`", "`user_name`", "`sex`", "`uuid`", "`age`"}
assert.Equal(t, expected, out)
})
}
func TestNewEq(t *testing.T) {
u := &mockedUser{
ID: "123456",
UserName: "wahaha",
}
out := NewEq(u)
fmt.Println(out)
actual := builder.Eq{"id": "123456", "user_name": "wahaha"}
assert.Equal(t, out, actual)
}
// @see https://github.com/go-xorm/builder
func TestBuilderSql(t *testing.T) {
u := &mockedUser{
ID: "123123",
}
fields := RawFieldNames(u)
eq := NewEq(u)
sql, args, err := builder.Select(fields...).From("user").Where(eq).ToSQL()
fmt.Println(sql, args, err)
actualSQL := "SELECT `id`,`user_name`,`sex`,`uuid`,`age` FROM user WHERE id=?"
actualArgs := []interface{}{"123123"}
assert.Equal(t, sql, actualSQL)
assert.Equal(t, args, actualArgs)
}
func TestBuildSqlDefaultValue(t *testing.T) {
eq := builder.Eq{}
eq["age"] = 0
eq["user_name"] = ""
t.Run("raw", func(t *testing.T) {
sql, args, err := builder.Select(userFieldsWithRawStringQuote...).From("user").Where(eq).ToSQL()
fmt.Println(sql, args, err)
actualSQL := "SELECT `id`,`user_name`,`sex`,`uuid`,`age` FROM user WHERE age=? AND user_name=?"
actualArgs := []interface{}{0, ""}
assert.Equal(t, sql, actualSQL)
assert.Equal(t, args, actualArgs)
})
t.Run("withour raw quote", func(t *testing.T) {
sql, args, err := builder.Select(userFieldsWithoutRawStringQuote...).From("user").Where(eq).ToSQL()
fmt.Println(sql, args, err)
actualSQL := "SELECT id,user_name,sex,uuid,age FROM user WHERE age=? AND user_name=?"
actualArgs := []interface{}{0, ""}
assert.Equal(t, sql, actualSQL)
assert.Equal(t, args, actualArgs)
})
}
func TestBuilderSqlIn(t *testing.T) {
u := &mockedUser{
Age: 18,
}
gtU := NewGt(u)
in := builder.In("id", []string{"1", "2", "3"})
sql, args, err := builder.Select(userFieldsWithRawStringQuote...).From("user").Where(in).And(gtU).ToSQL()
fmt.Println(sql, args, err)
actualSQL := "SELECT `id`,`user_name`,`sex`,`uuid`,`age` FROM user WHERE id IN (?,?,?) AND age>?"
actualArgs := []interface{}{"1", "2", "3", 18}
assert.Equal(t, sql, actualSQL)
assert.Equal(t, args, actualArgs)
}
func TestBuildSqlLike(t *testing.T) {
like := builder.Like{"name", "wang"}
sql, args, err := builder.Select(userFieldsWithRawStringQuote...).From("user").Where(like).ToSQL()
fmt.Println(sql, args, err)
actualSQL := "SELECT `id`,`user_name`,`sex`,`uuid`,`age` FROM user WHERE name LIKE ?"
actualArgs := []interface{}{"%wang%"}
assert.Equal(t, sql, actualSQL)
assert.Equal(t, args, actualArgs)
}
func TestJoin(t *testing.T) {
ret := PostgreSqlJoin([]string{"name", "age"})
assert.Equal(t, "name = $2, age = $3", ret)
}

View File

@@ -8,12 +8,14 @@ CREATE TABLE `user`
`mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',
`gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公\r开',
`nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',
`type` tinyint(1) COLLATE utf8mb4_general_ci DEFAULT 0 COMMENT '用户类型',
`create_time` timestamp NULL,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `name_index` (`name`),
UNIQUE KEY `name_index2` (`name`),
UNIQUE KEY `user_index` (`user`),
UNIQUE KEY `type_index` (`type`),
UNIQUE KEY `mobile_index` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@@ -13,6 +13,8 @@ import (
func genInsert(table Table, withCache, postgreSql bool) (string, string, error) {
keySet := collection.NewSet()
keyVariableSet := collection.NewSet()
keySet.AddStr(table.PrimaryCacheKey.DataKeyExpression)
keyVariableSet.AddStr(table.PrimaryCacheKey.KeyLeft)
for _, key := range table.UniqueCacheKey {
keySet.AddStr(key.DataKeyExpression)
keyVariableSet.AddStr(key.KeyLeft)

View File

@@ -6,6 +6,7 @@ import (
"github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/util"
su "github.com/tal-tech/go-zero/tools/goctl/util"
)
const indexPri = "PRIMARY"
@@ -144,14 +145,15 @@ func (m *InformationSchemaModel) FindIndex(db, table, column string) ([]*DbIndex
// Convert converts column data into Table
func (c *ColumnData) Convert() (*Table, error) {
var table Table
table.Table = c.Table
table.Db = c.Db
table.Table = su.EscapeGolangKeyword(c.Table)
table.Db = su.EscapeGolangKeyword(c.Db)
table.Columns = c.Columns
table.UniqueIndex = map[string][]*Column{}
table.NormalIndex = map[string][]*Column{}
m := make(map[string][]*Column)
for _, each := range c.Columns {
each.Name = su.EscapeGolangKeyword(each.Name)
each.Comment = util.TrimNewLine(each.Comment)
if each.Index != nil {
m[each.Index.IndexName] = append(m[each.Index.IndexName], each)

View File

@@ -10,6 +10,7 @@ import (
"github.com/tal-tech/go-zero/tools/goctl/model/sql/converter"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/util"
su "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
"github.com/zeromicro/ddl-parser/parser"
@@ -49,11 +50,12 @@ type (
// Parse parses ddl into golang structure
func Parse(filename, database string) ([]*Table, error) {
p := parser.NewParser()
tables, err := p.From(filename)
ts, err := p.From(filename)
if err != nil {
return nil, err
}
tables := GetSafeTables(ts)
indexNameGen := func(column ...string) string {
return strings.Join(column, "_")
}
@@ -167,7 +169,7 @@ func checkDuplicateUniqueIndex(uniqueIndex map[string][]*Field, tableName string
joinRet := strings.Join(list, ",")
if uniqueSet.Contains(joinRet) {
log.Warning("table %s: duplicate unique index %s", tableName, joinRet)
log.Warning("[checkDuplicateUniqueIndex]: table %s: duplicate unique index %s", tableName, joinRet)
delete(uniqueIndex, k)
continue
}
@@ -213,10 +215,10 @@ func convertColumns(columns []*parser.Column, primaryColumn string) (Primary, ma
if column.Constraint != nil {
if column.Name == primaryColumn {
if !column.Constraint.AutoIncrement && dataType == "int64" {
log.Warning("%s: The primary key is recommended to add constraint `AUTO_INCREMENT`", column.Name)
log.Warning("[convertColumns]: The primary key %q is recommended to add constraint `AUTO_INCREMENT`", column.Name)
}
} else if column.Constraint.NotNull && !column.Constraint.HasDefaultValue {
log.Warning("%s: The column is recommended to add constraint `DEFAULT`", column.Name)
log.Warning("[convertColumns]: The column %q is recommended to add constraint `DEFAULT`", column.Name)
}
}
@@ -302,7 +304,7 @@ func ConvertDataType(table *model.Table) (*Table, error) {
if len(each) == 1 {
one := each[0]
if one.Name == table.PrimaryKey.Name {
log.Warning("table %s: duplicate unique index with primary key, %s", table.Table, one.Name)
log.Warning("[ConvertDataType]: table q%, duplicate unique index with primary key: %q", table.Table, one.Name)
continue
}
}
@@ -316,7 +318,7 @@ func ConvertDataType(table *model.Table) (*Table, error) {
uniqueKey := strings.Join(uniqueJoin, ",")
if uniqueIndexSet.Contains(uniqueKey) {
log.Warning("table %s: duplicate unique index, %s", table.Table, uniqueKey)
log.Warning("[ConvertDataType]: table %q, duplicate unique index %q", table.Table, uniqueKey)
continue
}
@@ -351,3 +353,33 @@ func getTableFields(table *model.Table) (map[string]*Field, error) {
}
return fieldM, nil
}
func GetSafeTables(tables []*parser.Table) []*parser.Table {
var list []*parser.Table
for _, t := range tables {
table := GetSafeTable(t)
list = append(list, table)
}
return list
}
func GetSafeTable(table *parser.Table) *parser.Table {
table.Name = su.EscapeGolangKeyword(table.Name)
for _, c := range table.Columns {
c.Name = su.EscapeGolangKeyword(c.Name)
}
for _, e := range table.Constraints {
var uniqueKeys, primaryKeys []string
for _, u := range e.ColumnUniqueKey {
uniqueKeys = append(uniqueKeys, su.EscapeGolangKeyword(u))
}
for _, p := range e.ColumnPrimaryKey {
primaryKeys = append(primaryKeys, su.EscapeGolangKeyword(p))
}
e.ColumnUniqueKey = uniqueKeys
e.ColumnPrimaryKey = primaryKeys
}
return table
}

View File

@@ -8,11 +8,11 @@ var (
"strings"
{{if .time}}"time"{{end}}
"github.com/tal-tech/go-zero/core/stores/builder"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/sqlc"
"github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
)
`
// ImportsNoCache defines a import template for model in normal case
@@ -22,10 +22,10 @@ var (
"strings"
{{if .time}}"time"{{end}}
"github.com/tal-tech/go-zero/core/stores/builder"
"github.com/tal-tech/go-zero/core/stores/sqlc"
"github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
)
`
)

View File

@@ -2,7 +2,7 @@ package template
// Insert defines a template for insert code in model
var Insert = `
func (m *default{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result,error) {
func (m *default{{.upperStartCamelObject}}Model) Insert(data *{{.upperStartCamelObject}}) (sql.Result,error) {
{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
@@ -15,5 +15,5 @@ func (m *default{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelO
}
`
// InsertMethod defines a interface method template for insert code in model
var InsertMethod = `Insert(data {{.upperStartCamelObject}}) (sql.Result,error)`
// InsertMethod defines an interface method template for insert code in model
var InsertMethod = `Insert(data *{{.upperStartCamelObject}}) (sql.Result,error)`

View File

@@ -2,7 +2,7 @@ package template
// Update defines a template for generating update codes
var Update = `
func (m *default{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) error {
func (m *default{{.upperStartCamelObject}}Model) Update(data *{{.upperStartCamelObject}}) error {
{{if .withCache}}{{.keys}}
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
@@ -14,4 +14,4 @@ func (m *default{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelO
`
// UpdateMethod defines an interface method template for generating update codes
var UpdateMethod = `Update(data {{.upperStartCamelObject}}) error`
var UpdateMethod = `Update(data *{{.upperStartCamelObject}}) error`

Some files were not shown because too many files have changed in this diff Show More