mirror of https://github.com/status-im/consul.git
Implement missing HTTP host to ConsulResolver func for Connect SDK.
This commit is contained in:
parent
b3dd5c1aad
commit
af2901130d
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
|
@ -202,3 +203,95 @@ func (cr *ConsulResolver) queryOptions(ctx context.Context) *api.QueryOptions {
|
|||
}
|
||||
return q.WithContext(ctx)
|
||||
}
|
||||
|
||||
// ConsulResolverFromAddrFunc returns a function for constructing ConsulResolver
|
||||
// from a consul DNS formatted hostname (e.g. foo.service.consul or
|
||||
// foo.query.consul).
|
||||
//
|
||||
// Note, the returned ConsulResolver resolves the query via regular agent HTTP
|
||||
// discovery API. DNS is not needed or used for discovery, only the hostname
|
||||
// format re-used for consistency.
|
||||
func ConsulResolverFromAddrFunc(client *api.Client) func(addr string) (Resolver, error) {
|
||||
// Capture client dependency
|
||||
return func(addr string) (Resolver, error) {
|
||||
// Http clients might provide hostname and port
|
||||
host := strings.ToLower(stripPort(addr))
|
||||
|
||||
// For now we force use of `.consul` TLD regardless of the configured domain
|
||||
// on the cluster. That's because we don't know that domain here and it
|
||||
// would be really complicated to discover it inline here. We do however
|
||||
// need to be able to distingush a hostname with the optional datacenter
|
||||
// segment which we can't do unambiguously if we allow arbitrary trailing
|
||||
// domains.
|
||||
domain := ".consul"
|
||||
if !strings.HasSuffix(host, domain) {
|
||||
return nil, fmt.Errorf("invalid Consul DNS domain: note Connect SDK " +
|
||||
"currently requires use of .consul domain even if cluster is " +
|
||||
"configured with a different domain.")
|
||||
}
|
||||
|
||||
// Remove the domain suffix
|
||||
host = host[0 : len(host)-len(domain)]
|
||||
|
||||
parts := strings.Split(host, ".")
|
||||
numParts := len(parts)
|
||||
|
||||
r := &ConsulResolver{
|
||||
Client: client,
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
// Note that 3 segments may be a valid DNS name like
|
||||
// <tag>.<service>.service.consul but not one we support, it might also be
|
||||
// <service>.service.<datacenter>.consul which we do want to support so we
|
||||
// have to figure out if the last segment is supported keyword and if not
|
||||
// check if the supported keyword is further up...
|
||||
|
||||
// To simplify logic for now, we must match one of the following (not domain
|
||||
// is stripped):
|
||||
// <name>.[service|query]
|
||||
// <name>.[service|query].<dc>
|
||||
if numParts < 2 || numParts > 3 || !supportedTypeLabel(parts[1]) {
|
||||
return nil, fmt.Errorf("unsupported Consul DNS domain: must be either " +
|
||||
"<name>.service[.<datacenter>].consul or " +
|
||||
"<name>.query[.<datacenter>].consul")
|
||||
}
|
||||
|
||||
if numParts == 3 {
|
||||
// Must be datacenter case
|
||||
r.Datacenter = parts[2]
|
||||
}
|
||||
|
||||
// By know we must have a supported query type which means at least 2
|
||||
// elements with first 2 being name, and type respectively.
|
||||
r.Name = parts[0]
|
||||
switch parts[1] {
|
||||
case "service":
|
||||
r.Type = ConsulResolverTypeService
|
||||
case "query":
|
||||
r.Type = ConsulResolverTypePreparedQuery
|
||||
default:
|
||||
// This should never happen (tm) unless the supportedTypeLabel
|
||||
// implementation is changed and this switch isn't.
|
||||
return nil, fmt.Errorf("invalid discovery type")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
func supportedTypeLabel(label string) bool {
|
||||
return label == "service" || label == "query"
|
||||
}
|
||||
|
||||
// stripPort copied from net/url/url.go
|
||||
func stripPort(hostport string) string {
|
||||
colon := strings.IndexByte(hostport, ':')
|
||||
if colon == -1 {
|
||||
return hostport
|
||||
}
|
||||
if i := strings.IndexByte(hostport, ']'); i != -1 {
|
||||
return strings.TrimPrefix(hostport[:i], "[")
|
||||
}
|
||||
return hostport[:colon]
|
||||
}
|
||||
|
|
|
@ -216,3 +216,110 @@ func TestConsulResolver_Resolve(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsulResolverFromAddrFunc(t *testing.T) {
|
||||
// Don't need an actual instance since we don't do the service discovery but
|
||||
// we do want to assert the client is pass through correctly.
|
||||
client, err := api.NewClient(api.DefaultConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
addr string
|
||||
want Resolver
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "service",
|
||||
addr: "foo.service.consul",
|
||||
want: &ConsulResolver{
|
||||
Client: client,
|
||||
Namespace: "default",
|
||||
Name: "foo",
|
||||
Type: ConsulResolverTypeService,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "query",
|
||||
addr: "foo.query.consul",
|
||||
want: &ConsulResolver{
|
||||
Client: client,
|
||||
Namespace: "default",
|
||||
Name: "foo",
|
||||
Type: ConsulResolverTypePreparedQuery,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "service with dc",
|
||||
addr: "foo.service.dc2.consul",
|
||||
want: &ConsulResolver{
|
||||
Client: client,
|
||||
Datacenter: "dc2",
|
||||
Namespace: "default",
|
||||
Name: "foo",
|
||||
Type: ConsulResolverTypeService,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "query with dc",
|
||||
addr: "foo.query.dc2.consul",
|
||||
want: &ConsulResolver{
|
||||
Client: client,
|
||||
Datacenter: "dc2",
|
||||
Namespace: "default",
|
||||
Name: "foo",
|
||||
Type: ConsulResolverTypePreparedQuery,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid host:port",
|
||||
addr: "%%%",
|
||||
wantErr: "invalid Consul DNS domain",
|
||||
},
|
||||
{
|
||||
name: "custom domain",
|
||||
addr: "foo.service.my-consul.com",
|
||||
wantErr: "invalid Consul DNS domain",
|
||||
},
|
||||
{
|
||||
name: "unsupported query type",
|
||||
addr: "foo.connect.consul",
|
||||
wantErr: "unsupported Consul DNS domain",
|
||||
},
|
||||
{
|
||||
name: "unsupported query type and datacenter",
|
||||
addr: "foo.connect.dc1.consul",
|
||||
wantErr: "unsupported Consul DNS domain",
|
||||
},
|
||||
{
|
||||
name: "unsupported query type and datacenter",
|
||||
addr: "foo.connect.dc1.consul",
|
||||
wantErr: "unsupported Consul DNS domain",
|
||||
},
|
||||
{
|
||||
name: "unsupported tag filter",
|
||||
addr: "tag1.foo.service.consul",
|
||||
wantErr: "unsupported Consul DNS domain",
|
||||
},
|
||||
{
|
||||
name: "unsupported tag filter with DC",
|
||||
addr: "tag1.foo.service.dc1.consul",
|
||||
wantErr: "unsupported Consul DNS domain",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
fn := ConsulResolverFromAddrFunc(client)
|
||||
got, gotErr := fn(tt.addr)
|
||||
if tt.wantErr != "" {
|
||||
require.Error(gotErr)
|
||||
require.Contains(gotErr.Error(), tt.wantErr)
|
||||
} else {
|
||||
require.NoError(gotErr)
|
||||
require.Equal(tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,10 +69,11 @@ func NewService(serviceName string, client *api.Client) (*Service, error) {
|
|||
func NewServiceWithLogger(serviceName string, client *api.Client,
|
||||
logger *log.Logger) (*Service, error) {
|
||||
s := &Service{
|
||||
service: serviceName,
|
||||
client: client,
|
||||
logger: logger,
|
||||
tlsCfg: newDynamicTLSConfig(defaultTLSConfig()),
|
||||
service: serviceName,
|
||||
client: client,
|
||||
logger: logger,
|
||||
tlsCfg: newDynamicTLSConfig(defaultTLSConfig()),
|
||||
httpResolverFromAddr: ConsulResolverFromAddrFunc(client),
|
||||
}
|
||||
|
||||
// Set up root and leaf watches
|
||||
|
|
|
@ -252,3 +252,28 @@ func TestService_HTTPClient(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_HasDefaultHTTPResolverFromAddr(t *testing.T) {
|
||||
|
||||
client, err := api.NewClient(api.DefaultConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
s, err := NewService("foo", client)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sanity check this is actually set in constructor since we always override
|
||||
// it in tests. Full tests of the resolver func are in resolver_test.go
|
||||
require.NotNil(t, s.httpResolverFromAddr)
|
||||
|
||||
fn := s.httpResolverFromAddr
|
||||
|
||||
expected := &ConsulResolver{
|
||||
Client: client,
|
||||
Namespace: "default",
|
||||
Name: "foo",
|
||||
Type: ConsulResolverTypeService,
|
||||
}
|
||||
got, err := fn("foo.service.consul")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, got)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue