From 9681fa7ce6b31d6dc47e3b9f97289820bcb7d523 Mon Sep 17 00:00:00 2001 From: Mathilde Gilles Date: Thu, 29 Dec 2022 11:00:15 +0100 Subject: [PATCH] feat: add support for Consul prepared queries Consul prepared queries are very usefull as they provide automatic fallback between datacenters. This patch adds support for them while keeping the API backward compatible. As prepared queries cannot be watched like services, a new parameter `poll-interval` is introduced to control the poll frequency. --- README.md | 40 ++++++------ builder.go | 8 ++- consul.go | 94 +++++++++++++++++++++++++++- consul_test.go | 76 +++++++++++++++++++++- go.mod | 5 +- go.sum | 67 ++++++++------------ internal/mocks/consul.go | 90 ++++++++++++++++++++++++++ internal/mocks/resolverClientConn.go | 41 +++++++----- internal/mocks/servicer.go | 48 -------------- target.go | 39 ++++++++++-- target_test.go | 40 +++++++++++- 11 files changed, 410 insertions(+), 138 deletions(-) create mode 100644 internal/mocks/consul.go delete mode 100644 internal/mocks/servicer.go diff --git a/README.md b/README.md index 3b06170..d379362 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # GRPC consul resolver -Feature rich and easy-to-use resolver which return endpoints for service from the [Hashicorp Consul](https://www.consul.io) and watch for the changes. +Feature rich and easy-to-use resolver which return endpoints for service or prepared query from the [Hashicorp Consul](https://www.consul.io) and watch for the changes. -This library is *production ready* and will always *save backward-compatibility* +This library is _production ready_ and will always _save backward-compatibility_ ## Quick Start @@ -11,26 +11,30 @@ For using resolving endpoints from your [Hashicorp Consul](https://www.consul.io For full example see [this section](#example) ## Connection string + `consul://[user:password@]127.0.0.127:8555/my-service?[healthy=]&[wait=]&[near=]&[insecure=]&[limit=]&[tag=]&[token=]` -*Parameters:* - -| Name | Format | Description | -|--------------------|--------------------------|-------------------------------------------------------------------------------------------------------------------------------| -| tag | string | Select endpoints only with this tag | -| healthy | true/false | Return only endpoints which pass all health-checks. Default: false | -| wait | as in time.ParseDuration | Wait time for watch changes. Due this time period endpoints will be force refreshed. Default: inherits agent property | -| insecure | true/false | Allow insecure communication with Consul. Default: true | -| near | string | Sort endpoints by response duration. Can be efficient combine with `limit` parameter default: "_agent" | -| limit | int | Limit number of endpoints for the service. Default: no limit | -| timeout | as in time.ParseDuration | Http-client timeout. Default: 60s | -| max-backoff | as in time.ParseDuration | Max backoff time for reconnect to consul. Reconnects will start from 10ms to _max-backoff_ exponentialy with factor 2. Default: 1s | -| token | string | Consul token | -| dc | string | Consul datacenter to choose. Optional | -| allow-stale | true/false | Allow stale results from the agent. https://www.consul.io/api/features/consistency.html#stale | -| require-consistent | true/false | RequireConsistent forces the read to be fully consistent. This is more expensive but prevents ever performing a stale read. | +_Parameters:_ + +| Name | Format | Description | +| ------------------ | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | +| type | service/prepared_query | Whether to query a Consul service or a prepared query. Default: service | +| tag | string | Select endpoints only with this tag. Only when type=service. | +| healthy | true/false | Return only endpoints which pass all health-checks. Only when type=service. Default: false | +| wait | as in time.ParseDuration | Wait time for watch changes. Due this time period endpoints will be force refreshed. Only when type=service. Default: inherits agent property | +| insecure | true/false | Allow insecure communication with Consul. Default: true | +| near | string | Sort endpoints by response duration. Can be efficient combine with `limit` parameter default: "\_agent" | +| limit | int | Limit number of endpoints for the service. Default: no limit | +| timeout | as in time.ParseDuration | Http-client timeout. Default: 60s | +| max-backoff | as in time.ParseDuration | Max backoff time for reconnect to consul. Reconnects will start from 10ms to _max-backoff_ exponentialy with factor 2. Default: 1s | +| poll-interval | as in time.ParseDuration | How often to poll prepared queries. Only when type=prepared_query. Default: 30s | +| token | string | Consul token | +| dc | string | Consul datacenter to choose. Optional | +| allow-stale | true/false | Allow stale results from the agent. https://www.consul.io/api/features/consistency.html#stale | +| require-consistent | true/false | RequireConsistent forces the read to be fully consistent. This is more expensive but prevents ever performing a stale read. | ## Example + ```go package main diff --git a/builder.go b/builder.go index e300a40..2ec2d1a 100644 --- a/builder.go +++ b/builder.go @@ -29,7 +29,13 @@ func (b *builder) Build(url resolver.Target, cc resolver.ClientConn, opts resolv ctx, cancel := context.WithCancel(context.Background()) pipe := make(chan []string) - go watchConsulService(ctx, cli.Health(), tgt, pipe) + switch tgt.Type { + case targetTypeService: + go watchConsulService(ctx, cli.Health(), tgt, pipe) + case targetTypePreparedQuery: + go watchPreparedQuery(ctx, cli.PreparedQuery(), tgt, pipe) + } + go populateEndpoints(ctx, cc, pipe) return &resolvr{cancelFunc: cancel}, nil diff --git a/consul.go b/consul.go index b63c6e0..4313496 100644 --- a/consul.go +++ b/consul.go @@ -32,11 +32,15 @@ func (r *resolvr) Close() { } //go:generate mockgen -package mocks -destination internal/mocks/resolverClientConn.go google.golang.org/grpc/resolver ClientConn -//go:generate mockgen -package mocks -destination internal/mocks/servicer.go -source consul.go servicer +//go:generate mockgen -package mocks -destination internal/mocks/consul.go -source consul.go servicer querier type servicer interface { Service(string, string, bool, *api.QueryOptions) ([]*api.ServiceEntry, *api.QueryMeta, error) } +type querier interface { + Execute(string, *api.QueryOptions) (*api.PreparedQueryExecuteResponse, *api.QueryMeta, error) +} + func watchConsulService(ctx context.Context, s servicer, tgt target, out chan<- []string) { res := make(chan []string) quit := make(chan struct{}) @@ -50,7 +54,7 @@ func watchConsulService(ctx context.Context, s servicer, tgt target, out chan<- var lastIndex uint64 for { ss, meta, err := s.Service( - tgt.Service, + tgt.Target, tgt.Tag, tgt.Healthy, &api.QueryOptions{ @@ -125,6 +129,92 @@ func watchConsulService(ctx context.Context, s servicer, tgt target, out chan<- } } +func watchPreparedQuery(ctx context.Context, q querier, tgt target, out chan<- []string) { + res := make(chan []string) + quit := make(chan struct{}) + bck := &backoff.Backoff{ + Factor: 2, + Jitter: true, + Min: 10 * time.Millisecond, + Max: tgt.MaxBackoff, + } + go func() { + ticker := time.NewTicker(tgt.PollInterval) + defer ticker.Stop() + + for { + ss, meta, err := q.Execute( + tgt.Target, + &api.QueryOptions{ + Near: tgt.Near, + Datacenter: tgt.Dc, + AllowStale: tgt.AllowStale, + RequireConsistent: tgt.RequireConsistent, + }, + ) + if err != nil { + // No need to continue if the context is done/cancelled. + // We check that here directly because the check for the closed quit channel + // at the end of the loop is not reached when calling continue here. + select { + case <-quit: + return + default: + grpclog.Errorf("[Consul resolver] Couldn't fetch endpoints. target={%s}; error={%v}", tgt.String(), err) + time.Sleep(bck.Duration()) + continue + } + } + bck.Reset() + grpclog.Infof("[Consul resolver] %d endpoints fetched in %s for target={%s}", + len(ss.Nodes), + meta.RequestTime, + tgt.String(), + ) + + ee := make([]string, 0, len(ss.Nodes)) + for _, s := range ss.Nodes { + address := s.Service.Address + if s.Service.Address == "" { + address = s.Node.Address + } + ee = append(ee, fmt.Sprintf("%s:%d", address, s.Service.Port)) + } + + if tgt.Limit != 0 && len(ee) > tgt.Limit { + ee = ee[:tgt.Limit] + } + select { + case res <- ee: + <-ticker.C + continue + case <-quit: + return + } + } + }() + + for { + // If in the below select both channels have values that can be read, + // Go picks one pseudo-randomly. + // But when the context is canceled we want to act upon it immediately. + if ctx.Err() != nil { + // Close quit so the goroutine returns and doesn't leak. + // Do NOT close res because that can lead to panics in the goroutine. + // res will be garbage collected at some point. + close(quit) + return + } + select { + case ee := <-res: + out <- ee + case <-ctx.Done(): + close(quit) + return + } + } +} + func populateEndpoints(ctx context.Context, clientConn resolver.ClientConn, input <-chan []string) { for { select { diff --git a/consul_test.go b/consul_test.go index a90bb2c..2027814 100644 --- a/consul_test.go +++ b/consul_test.go @@ -70,7 +70,7 @@ func TestWatchConsulService(t *testing.T) { errorFromService error want []string }{ - {"simple", target{Service: "svc", Wait: time.Second}, + {"simple", target{Target: "svc", Wait: time.Second}, []*api.ServiceEntry{ &api.ServiceEntry{ Service: &api.AgentService{Address: "127.0.0.1", Port: 1024}, @@ -102,7 +102,7 @@ func TestWatchConsulService(t *testing.T) { } }() fconsul := mocks.NewMockservicer(ctrl) - fconsul.EXPECT().Service(tt.tgt.Service, tt.tgt.Tag, tt.tgt.Healthy, &api.QueryOptions{ + fconsul.EXPECT().Service(tt.tgt.Target, tt.tgt.Tag, tt.tgt.Healthy, &api.QueryOptions{ WaitIndex: 0, Near: tt.tgt.Near, WaitTime: tt.tgt.Wait, @@ -112,7 +112,7 @@ func TestWatchConsulService(t *testing.T) { }). Times(1). Return(tt.services, &api.QueryMeta{LastIndex: 1}, tt.errorFromService) - fconsul.EXPECT().Service(tt.tgt.Service, tt.tgt.Tag, tt.tgt.Healthy, &api.QueryOptions{ + fconsul.EXPECT().Service(tt.tgt.Target, tt.tgt.Tag, tt.tgt.Healthy, &api.QueryOptions{ WaitIndex: 1, Near: tt.tgt.Near, WaitTime: tt.tgt.Wait, @@ -137,3 +137,73 @@ func TestWatchConsulService(t *testing.T) { }) } } + +func TestWatchPeparedQuery(t *testing.T) { + tests := []struct { + name string + tgt target + responses []*api.PreparedQueryExecuteResponse + errorFromService error + want [][]string + }{ + {"simple", target{Target: "myquery", PollInterval: 100 * time.Millisecond}, + []*api.PreparedQueryExecuteResponse{ + { + Nodes: []api.ServiceEntry{ + { + Service: &api.AgentService{Address: "127.0.0.1", Port: 1024}, + }, + }, + }, + { + Nodes: []api.ServiceEntry{ + { + Service: &api.AgentService{Address: "127.0.0.2", Port: 1024}, + }, + }, + }, + }, + nil, + [][]string{ + {"127.0.0.1:1024"}, + {"127.0.0.2:1024"}, + }, + }, + // TODO: Add more tests-cases + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + i := 0 + var out = make(chan []string, 1) + fconsul := mocks.NewMockquerier(ctrl) + fconsul.EXPECT().Execute(tt.tgt.Target, &api.QueryOptions{ + Near: tt.tgt.Near, + Datacenter: tt.tgt.Dc, + AllowStale: tt.tgt.AllowStale, + RequireConsistent: tt.tgt.RequireConsistent, + }). + Times(len(tt.responses)). + DoAndReturn(func(arg0 string, arg1 *api.QueryOptions) (*api.PreparedQueryExecuteResponse, *api.QueryMeta, error) { + v := tt.responses[i] + i++ + return v, &api.QueryMeta{}, tt.errorFromService + }) + + go watchPreparedQuery(ctx, fconsul, tt.tgt, out) + + for _, want := range tt.want { + select { + case <-ctx.Done(): + return + case got := <-out: + require.Equal(t, want, got) + } + } + }) + } +} diff --git a/go.mod b/go.mod index 0e4bb9f..73468ee 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/armon/go-metrics v0.3.2 // indirect github.com/go-playground/form v3.1.4+incompatible - github.com/golang/mock v1.1.1 + github.com/golang/mock v1.6.0 github.com/google/btree v1.0.0 // indirect github.com/hashicorp/consul/api v1.3.0 github.com/hashicorp/go-immutable-radix v1.1.0 // indirect @@ -16,9 +16,6 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.4.0 - golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect - golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect - golang.org/x/text v0.3.2 // indirect google.golang.org/genproto v0.0.0-20200210034751-acff78025515 // indirect google.golang.org/grpc v1.27.1 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect diff --git a/go.sum b/go.sum index 82fca19..6fdcb14 100644 --- a/go.sum +++ b/go.sum @@ -6,7 +6,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.2 h1:EyUnxyP2yaGpLgMiuyyz8sHnByqeTJUfGs72pdH0i4A= github.com/armon/go-metrics v0.3.2/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= @@ -36,20 +35,17 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= @@ -64,7 +60,6 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -73,7 +68,6 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -84,7 +78,6 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -92,7 +85,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.5 h1:ZynDUIQiA8usmRgPdGPHFdPnb1wgGI9tK3mO9hcAJjc= github.com/hashicorp/serf v0.8.5/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k= @@ -115,7 +107,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -131,12 +122,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -163,75 +152,74 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 h1:x6r4Jo0KNzOOzYd8lbcRsqjuqEASK6ob3auvWYM4/8U= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/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= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200210034751-acff78025515 h1:SlofR15fzhHsop1cmdda0uNO88mGfustCgMZoy2VGfA= google.golang.org/genproto v0.0.0-20200210034751-acff78025515/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= @@ -241,16 +229,13 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/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/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/mocks/consul.go b/internal/mocks/consul.go new file mode 100644 index 0000000..154b4b3 --- /dev/null +++ b/internal/mocks/consul.go @@ -0,0 +1,90 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: consul.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + api "github.com/hashicorp/consul/api" +) + +// Mockservicer is a mock of servicer interface. +type Mockservicer struct { + ctrl *gomock.Controller + recorder *MockservicerMockRecorder +} + +// MockservicerMockRecorder is the mock recorder for Mockservicer. +type MockservicerMockRecorder struct { + mock *Mockservicer +} + +// NewMockservicer creates a new mock instance. +func NewMockservicer(ctrl *gomock.Controller) *Mockservicer { + mock := &Mockservicer{ctrl: ctrl} + mock.recorder = &MockservicerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mockservicer) EXPECT() *MockservicerMockRecorder { + return m.recorder +} + +// Service mocks base method. +func (m *Mockservicer) Service(arg0, arg1 string, arg2 bool, arg3 *api.QueryOptions) ([]*api.ServiceEntry, *api.QueryMeta, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Service", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]*api.ServiceEntry) + ret1, _ := ret[1].(*api.QueryMeta) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Service indicates an expected call of Service. +func (mr *MockservicerMockRecorder) Service(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Service", reflect.TypeOf((*Mockservicer)(nil).Service), arg0, arg1, arg2, arg3) +} + +// Mockquerier is a mock of querier interface. +type Mockquerier struct { + ctrl *gomock.Controller + recorder *MockquerierMockRecorder +} + +// MockquerierMockRecorder is the mock recorder for Mockquerier. +type MockquerierMockRecorder struct { + mock *Mockquerier +} + +// NewMockquerier creates a new mock instance. +func NewMockquerier(ctrl *gomock.Controller) *Mockquerier { + mock := &Mockquerier{ctrl: ctrl} + mock.recorder = &MockquerierMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mockquerier) EXPECT() *MockquerierMockRecorder { + return m.recorder +} + +// Execute mocks base method. +func (m *Mockquerier) Execute(arg0 string, arg1 *api.QueryOptions) (*api.PreparedQueryExecuteResponse, *api.QueryMeta, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Execute", arg0, arg1) + ret0, _ := ret[0].(*api.PreparedQueryExecuteResponse) + ret1, _ := ret[1].(*api.QueryMeta) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Execute indicates an expected call of Execute. +func (mr *MockquerierMockRecorder) Execute(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*Mockquerier)(nil).Execute), arg0, arg1) +} diff --git a/internal/mocks/resolverClientConn.go b/internal/mocks/resolverClientConn.go index 12a7dc2..be4061d 100644 --- a/internal/mocks/resolverClientConn.go +++ b/internal/mocks/resolverClientConn.go @@ -5,83 +5,94 @@ package mocks import ( + reflect "reflect" + gomock "github.com/golang/mock/gomock" resolver "google.golang.org/grpc/resolver" serviceconfig "google.golang.org/grpc/serviceconfig" - reflect "reflect" ) -// MockClientConn is a mock of ClientConn interface +// MockClientConn is a mock of ClientConn interface. type MockClientConn struct { ctrl *gomock.Controller recorder *MockClientConnMockRecorder } -// MockClientConnMockRecorder is the mock recorder for MockClientConn +// MockClientConnMockRecorder is the mock recorder for MockClientConn. type MockClientConnMockRecorder struct { mock *MockClientConn } -// NewMockClientConn creates a new mock instance +// NewMockClientConn creates a new mock instance. func NewMockClientConn(ctrl *gomock.Controller) *MockClientConn { mock := &MockClientConn{ctrl: ctrl} mock.recorder = &MockClientConnMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockClientConn) EXPECT() *MockClientConnMockRecorder { return m.recorder } -// NewAddress mocks base method +// NewAddress mocks base method. func (m *MockClientConn) NewAddress(arg0 []resolver.Address) { + m.ctrl.T.Helper() m.ctrl.Call(m, "NewAddress", arg0) } -// NewAddress indicates an expected call of NewAddress +// NewAddress indicates an expected call of NewAddress. func (mr *MockClientConnMockRecorder) NewAddress(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAddress", reflect.TypeOf((*MockClientConn)(nil).NewAddress), arg0) } -// NewServiceConfig mocks base method +// NewServiceConfig mocks base method. func (m *MockClientConn) NewServiceConfig(arg0 string) { + m.ctrl.T.Helper() m.ctrl.Call(m, "NewServiceConfig", arg0) } -// NewServiceConfig indicates an expected call of NewServiceConfig +// NewServiceConfig indicates an expected call of NewServiceConfig. func (mr *MockClientConnMockRecorder) NewServiceConfig(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewServiceConfig", reflect.TypeOf((*MockClientConn)(nil).NewServiceConfig), arg0) } -// ParseServiceConfig mocks base method +// ParseServiceConfig mocks base method. func (m *MockClientConn) ParseServiceConfig(arg0 string) *serviceconfig.ParseResult { + m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParseServiceConfig", arg0) ret0, _ := ret[0].(*serviceconfig.ParseResult) return ret0 } -// ParseServiceConfig indicates an expected call of ParseServiceConfig +// ParseServiceConfig indicates an expected call of ParseServiceConfig. func (mr *MockClientConnMockRecorder) ParseServiceConfig(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParseServiceConfig", reflect.TypeOf((*MockClientConn)(nil).ParseServiceConfig), arg0) } -// ReportError mocks base method +// ReportError mocks base method. func (m *MockClientConn) ReportError(arg0 error) { + m.ctrl.T.Helper() m.ctrl.Call(m, "ReportError", arg0) } -// ReportError indicates an expected call of ReportError +// ReportError indicates an expected call of ReportError. func (mr *MockClientConnMockRecorder) ReportError(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportError", reflect.TypeOf((*MockClientConn)(nil).ReportError), arg0) } -// UpdateState mocks base method +// UpdateState mocks base method. func (m *MockClientConn) UpdateState(arg0 resolver.State) { + m.ctrl.T.Helper() m.ctrl.Call(m, "UpdateState", arg0) } -// UpdateState indicates an expected call of UpdateState +// UpdateState indicates an expected call of UpdateState. func (mr *MockClientConnMockRecorder) UpdateState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateState", reflect.TypeOf((*MockClientConn)(nil).UpdateState), arg0) } diff --git a/internal/mocks/servicer.go b/internal/mocks/servicer.go deleted file mode 100644 index 28c955e..0000000 --- a/internal/mocks/servicer.go +++ /dev/null @@ -1,48 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: consul.go - -// Package mocks is a generated GoMock package. -package mocks - -import ( - gomock "github.com/golang/mock/gomock" - api "github.com/hashicorp/consul/api" - reflect "reflect" -) - -// Mockservicer is a mock of servicer interface -type Mockservicer struct { - ctrl *gomock.Controller - recorder *MockservicerMockRecorder -} - -// MockservicerMockRecorder is the mock recorder for Mockservicer -type MockservicerMockRecorder struct { - mock *Mockservicer -} - -// NewMockservicer creates a new mock instance -func NewMockservicer(ctrl *gomock.Controller) *Mockservicer { - mock := &Mockservicer{ctrl: ctrl} - mock.recorder = &MockservicerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *Mockservicer) EXPECT() *MockservicerMockRecorder { - return m.recorder -} - -// Service mocks base method -func (m *Mockservicer) Service(arg0, arg1 string, arg2 bool, arg3 *api.QueryOptions) ([]*api.ServiceEntry, *api.QueryMeta, error) { - ret := m.ctrl.Call(m, "Service", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].([]*api.ServiceEntry) - ret1, _ := ret[1].(*api.QueryMeta) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Service indicates an expected call of Service -func (mr *MockservicerMockRecorder) Service(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Service", reflect.TypeOf((*Mockservicer)(nil).Service), arg0, arg1, arg2, arg3) -} diff --git a/target.go b/target.go index 703bbad..ae3f53a 100644 --- a/target.go +++ b/target.go @@ -12,14 +12,23 @@ import ( "github.com/pkg/errors" ) +const ( + targetTypeService = "service" + targetTypePreparedQuery = "prepared_query" + + defaultPollInterval = 30 * time.Second +) + type target struct { + Type string `form:"type"` Addr string `form:"-"` User string `form:"-"` Password string `form:"-"` - Service string `form:"-"` + Target string `form:"-"` Wait time.Duration `form:"wait"` Timeout time.Duration `form:"timeout"` MaxBackoff time.Duration `form:"max-backoff"` + PollInterval time.Duration `form:"poll-interval"` Tag string `form:"tag"` Near string `form:"near"` Limit int `form:"limit"` @@ -34,10 +43,11 @@ type target struct { } func (t *target) String() string { - return fmt.Sprintf("service='%s' healthy='%t' tag='%s'", t.Service, t.Healthy, t.Tag) + return fmt.Sprintf("%s='%s' healthy='%t' tag='%s'", t.Type, t.Target, t.Healthy, t.Tag) } -// parseURL with parameters +// parseURL with parameters +// // see README.md for the actual format // URL schema will stay stable in the future for backward compatibility func parseURL(u string) (target, error) { @@ -56,7 +66,7 @@ func parseURL(u string) (target, error) { tgt.User = rawURL.User.Username() tgt.Password, _ = rawURL.User.Password() tgt.Addr = rawURL.Host - tgt.Service = strings.TrimLeft(rawURL.Path, "/") + tgt.Target = strings.TrimLeft(rawURL.Path, "/") decoder := form.NewDecoder() decoder.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) { return time.ParseDuration(vals[0]) @@ -66,12 +76,33 @@ func parseURL(u string) (target, error) { if err != nil { return target{}, errors.Wrap(err, "Malformed URL parameters") } + if tgt.Type == "" { + tgt.Type = targetTypeService + } + if tgt.Type != targetTypeService && tgt.Type != targetTypePreparedQuery { + return target{}, errors.Errorf(`"type" must be either %q or %q`, targetTypeService, targetTypePreparedQuery) + } + if tgt.Type == targetTypeService && tgt.PollInterval > 0 { + return target{}, errors.Errorf(`"poll-interval" can only be set when type=%q`, targetTypePreparedQuery) + } + if tgt.Type == targetTypePreparedQuery && tgt.Wait > 0 { + return target{}, errors.Errorf(`"wait" can only be set when type=%q`, targetTypeService) + } + if tgt.Type == targetTypePreparedQuery && tgt.Healthy { + return target{}, errors.Errorf(`"healthy" can only be set when type=%q`, targetTypeService) + } + if tgt.Type == targetTypePreparedQuery && tgt.Tag != "" { + return target{}, errors.Errorf(`"tag" can only be set when type=%q`, targetTypeService) + } if len(tgt.Near) == 0 { tgt.Near = "_agent" } if tgt.MaxBackoff == 0 { tgt.MaxBackoff = time.Second } + if tgt.Type == targetTypePreparedQuery && tgt.PollInterval == 0 { + tgt.PollInterval = defaultPollInterval + } return tgt, nil } diff --git a/target_test.go b/target_test.go index 8fd447c..3764e6a 100644 --- a/target_test.go +++ b/target_test.go @@ -17,8 +17,9 @@ func Test_parseURL(t *testing.T) { }{ {"simple", "consul://127.0.0.127:8555/my-service", target{ + Type: "service", Addr: "127.0.0.127:8555", - Service: "my-service", + Target: "my-service", Near: "_agent", MaxBackoff: time.Second, }, @@ -26,10 +27,11 @@ func Test_parseURL(t *testing.T) { }, {"all-args", "consul://user:password@127.0.0.127:8555/my-service?wait=14s&near=host&insecure=true&limit=1&tag=production&token=test_token&max-backoff=2s&dc=xx&allow-stale=true&require-consistent=true", target{ + Type: "service", Addr: "127.0.0.127:8555", User: "user", Password: "password", - Service: "my-service", + Target: "my-service", Near: "host", Wait: 14 * time.Second, TLSInsecure: true, @@ -43,6 +45,40 @@ func Test_parseURL(t *testing.T) { }, false, }, + {"prepared-query", "consul://127.0.0.127:8555/my-query?type=prepared_query", + target{ + Type: "prepared_query", + Addr: "127.0.0.127:8555", + Target: "my-query", + Near: "_agent", + MaxBackoff: time.Second, + PollInterval: 30 * time.Second, + }, + false, + }, + {"prepared-query-poll-interval", "consul://127.0.0.127:8555/my-query?type=prepared_query&poll-interval=10s", + target{ + Type: "prepared_query", + Addr: "127.0.0.127:8555", + Target: "my-query", + Near: "_agent", + MaxBackoff: time.Second, + PollInterval: 10 * time.Second, + }, + false, + }, + {"prepared-query-with-wait", "consul://127.0.0.127:8555/my-query?type=prepared_query&wait=10s", + target{}, + true, + }, + {"prepared-query-with-tag", "consul://127.0.0.127:8555/my-query?type=prepared_query&tag=my-tag", + target{}, + true, + }, + {"prepared-query-with-healthy", "consul://127.0.0.127:8555/my-query?type=prepared_query&healthy=true", + target{}, + true, + }, {"bad-scheme", "127.0.0.127:8555/my-service", target{}, true,