Skip to content

Commit

Permalink
Support Consul namespaces/partitions/peering (#1822)
Browse files Browse the repository at this point in the history
* Support Consul namespaces/partitions/peering

* 1.16.2 => 1.17.0
  • Loading branch information
lkysow authored Nov 7, 2023
1 parent cce3b51 commit d0f9f1a
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 27 deletions.
2 changes: 1 addition & 1 deletion dependency/catalog_node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func TestCatalogNodeQuery_Fetch(t *testing.T) {
},
Meta: map[string]string{
"consul-network-segment": "",
"consul-version": "1.16.2",
"consul-version": "1.17.0",
},
},
Services: []*CatalogNodeService{
Expand Down
2 changes: 1 addition & 1 deletion dependency/catalog_nodes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func TestCatalogNodesQuery_Fetch(t *testing.T) {
},
Meta: map[string]string{
"consul-network-segment": "",
"consul-version": "1.16.2",
"consul-version": "1.17.0",
},
},
},
Expand Down
4 changes: 2 additions & 2 deletions dependency/catalog_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func TestCatalogServiceQuery_Fetch(t *testing.T) {
},
NodeMeta: map[string]string{
"consul-network-segment": "",
"consul-version": "1.16.2",
"consul-version": "1.17.0",
},
ServiceID: "consul",
ServiceName: "consul",
Expand All @@ -189,7 +189,7 @@ func TestCatalogServiceQuery_Fetch(t *testing.T) {
},
NodeMeta: map[string]string{
"consul-network-segment": "",
"consul-version": "1.16.2",
"consul-version": "1.17.0",
},
ServiceID: "service-meta",
ServiceName: "service-meta",
Expand Down
31 changes: 31 additions & 0 deletions dependency/dependency.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
keyRe = `/?(?P<key>[^@]+)`
filterRe = `(\|(?P<filter>[[:word:]\,]+))?`
serviceNameRe = `(?P<name>[[:word:]\-\_]+)`
queryRe = `(\?(?P<query>[[:word:]\-\_\=\&]+))?`
nodeNameRe = `(?P<name>[[:word:]\.\-\_]+)`
nearRe = `(~(?P<near>[[:word:]\.\-\_]+))?`
prefixRe = `/?(?P<prefix>[^@]+)`
Expand Down Expand Up @@ -66,6 +67,9 @@ type QueryOptions struct {
VaultGrace time.Duration
WaitIndex uint64
WaitTime time.Duration
ConsulPeer string
ConsulPartition string
ConsulNamespace string
}

func (q *QueryOptions) Merge(o *QueryOptions) *QueryOptions {
Expand Down Expand Up @@ -117,13 +121,28 @@ func (q *QueryOptions) Merge(o *QueryOptions) *QueryOptions {
r.WaitTime = o.WaitTime
}

if o.ConsulNamespace != "" {
r.ConsulNamespace = o.ConsulNamespace
}

if o.ConsulPartition != "" {
r.ConsulPartition = o.ConsulPartition
}

if o.ConsulPeer != "" {
r.ConsulPeer = o.ConsulPeer
}

return &r
}

func (q *QueryOptions) ToConsulOpts() *consulapi.QueryOptions {
return &consulapi.QueryOptions{
AllowStale: q.AllowStale,
Datacenter: q.Datacenter,
Namespace: q.ConsulNamespace,
Partition: q.ConsulPartition,
Peer: q.ConsulPeer,
Near: q.Near,
RequireConsistent: q.RequireConsistent,
WaitIndex: q.WaitIndex,
Expand Down Expand Up @@ -162,6 +181,18 @@ func (q *QueryOptions) String() string {
u.Add("region", q.Region)
}

if q.ConsulNamespace != "" {
u.Add(QueryNamespace, q.ConsulNamespace)
}

if q.ConsulPeer != "" {
u.Add(QueryPeer, q.ConsulPeer)
}

if q.ConsulPartition != "" {
u.Add(QueryPartition, q.ConsulPartition)
}

if q.Near != "" {
u.Add("near", q.Near)
}
Expand Down
67 changes: 51 additions & 16 deletions dependency/health_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const (
HealthCritical = "critical"
HealthMaint = "maintenance"

QueryNamespace = "ns"
QueryPartition = "partition"
QueryPeer = "peer"

NodeMaint = "_node_maintenance"
ServiceMaint = "_service_maintenance:"
)
Expand All @@ -32,7 +36,7 @@ var (
_ Dependency = (*HealthServiceQuery)(nil)

// HealthServiceQueryRe is the regular expression to use.
HealthServiceQueryRe = regexp.MustCompile(`\A` + tagRe + serviceNameRe + dcRe + nearRe + filterRe + `\z`)
HealthServiceQueryRe = regexp.MustCompile(`\A` + tagRe + serviceNameRe + queryRe + dcRe + nearRe + filterRe + `\z`)
)

func init() {
Expand Down Expand Up @@ -62,12 +66,15 @@ type HealthService struct {
type HealthServiceQuery struct {
stopCh chan struct{}

dc string
filters []string
name string
near string
tag string
connect bool
dc string
filters []string
name string
near string
tag string
connect bool
partition string
peer string
namespace string
}

// NewHealthServiceQuery processes the strings to build a service dependency.
Expand Down Expand Up @@ -110,14 +117,39 @@ func healthServiceQuery(s string, connect bool) (*HealthServiceQuery, error) {
filters = []string{HealthPassing}
}

// Parse optional query into key pairs.
queryParams := url.Values{}
if queryRaw := m["query"]; queryRaw != "" {
var err error
queryParams, err = url.ParseQuery(queryRaw)
if err != nil {
return nil, fmt.Errorf(
"health.service: invalid query: %q: %s", queryRaw, err)
}
// Validate keys.
for key := range queryParams {
switch key {
case QueryNamespace,
QueryPeer,
QueryPartition:
default:
return nil,
fmt.Errorf("health.service: invalid query parameter key %q in query %q: supported keys: %s,%s,%s", key, queryRaw, QueryNamespace, QueryPeer, QueryPartition)
}
}
}

return &HealthServiceQuery{
stopCh: make(chan struct{}, 1),
dc: m["dc"],
filters: filters,
name: m["name"],
near: m["near"],
tag: m["tag"],
connect: connect,
stopCh: make(chan struct{}, 1),
dc: m["dc"],
filters: filters,
name: m["name"],
near: m["near"],
tag: m["tag"],
connect: connect,
namespace: queryParams.Get(QueryNamespace),
peer: queryParams.Get(QueryPeer),
partition: queryParams.Get(QueryPartition),
}, nil
}

Expand All @@ -131,8 +163,11 @@ func (d *HealthServiceQuery) Fetch(clients *ClientSet, opts *QueryOptions) (inte
}

opts = opts.Merge(&QueryOptions{
Datacenter: d.dc,
Near: d.near,
Datacenter: d.dc,
Near: d.near,
ConsulNamespace: d.namespace,
ConsulPartition: d.partition,
ConsulPeer: d.peer,
})

u := &url.URL{
Expand Down
100 changes: 95 additions & 5 deletions dependency/health_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ func TestNewHealthServiceQuery(t *testing.T) {
nil,
true,
},
{
"query_only",
"?ns=foo",
nil,
true,
},
{
name: "invalid query param (unsupported key)",
i: "name?unsupported=test",
err: true,
},
{
"name",
"name",
Expand Down Expand Up @@ -126,6 +137,85 @@ func TestNewHealthServiceQuery(t *testing.T) {
},
false,
},
{
"name_partition",
"name?partition=foo",
&HealthServiceQuery{
filters: []string{"passing"},
name: "name",
partition: "foo",
},
false,
},
{
"name_peer",
"name?peer=foo",
&HealthServiceQuery{
filters: []string{"passing"},
name: "name",
peer: "foo",
},
false,
},
{
"name_ns",
"name?ns=foo",
&HealthServiceQuery{
filters: []string{"passing"},
name: "name",
namespace: "foo",
},
false,
},
{
"name_ns_peer_partition",
"name?ns=foo&peer=bar&partition=baz",
&HealthServiceQuery{
filters: []string{"passing"},
name: "name",
namespace: "foo",
peer: "bar",
partition: "baz",
},
false,
},
{
"namespace set twice should use first",
"name?ns=foo&ns=bar",
&HealthServiceQuery{
filters: []string{"passing"},
name: "name",
namespace: "foo",
},
false,
},
{
"empty value in query param",
"name?ns=&peer=&partition=",
&HealthServiceQuery{
filters: []string{"passing"},
name: "name",
namespace: "",
peer: "",
partition: "",
},
false,
},
{
"query with other parameters",
"tag.name?peer=foo&ns=bar&partition=baz@dc2~near",
&HealthServiceQuery{
filters: []string{"passing"},
tag: "tag",
name: "name",
dc: "dc2",
near: "near",
peer: "foo",
namespace: "bar",
partition: "baz",
},
false,
},
}

for i, tc := range cases {
Expand Down Expand Up @@ -182,7 +272,7 @@ func TestHealthConnectServiceQuery_Fetch(t *testing.T) {
Tags: ServiceTags([]string{}),
NodeMeta: map[string]string{
"consul-network-segment": "",
"consul-version": "1.16.2",
"consul-version": "1.17.0",
},
Weights: api.AgentWeights{
Passing: 1,
Expand Down Expand Up @@ -240,7 +330,7 @@ func TestHealthServiceQuery_Fetch(t *testing.T) {
},
NodeMeta: map[string]string{
"consul-network-segment": "",
"consul-version": "1.16.2",
"consul-version": "1.17.0",
},
ServiceMeta: map[string]string{},
Address: testConsul.Config.Bind,
Expand Down Expand Up @@ -274,7 +364,7 @@ func TestHealthServiceQuery_Fetch(t *testing.T) {
},
NodeMeta: map[string]string{
"consul-network-segment": "",
"consul-version": "1.16.2",
"consul-version": "1.17.0",
},
ServiceMeta: map[string]string{},
Address: testConsul.Config.Bind,
Expand Down Expand Up @@ -303,7 +393,7 @@ func TestHealthServiceQuery_Fetch(t *testing.T) {
},
NodeMeta: map[string]string{
"consul-network-segment": "",
"consul-version": "1.16.2",
"consul-version": "1.17.0",
},
ServiceMeta: map[string]string{
"meta1": "value1",
Expand Down Expand Up @@ -333,7 +423,7 @@ func TestHealthServiceQuery_Fetch(t *testing.T) {
},
NodeMeta: map[string]string{
"consul-network-segment": "",
"consul-version": "1.16.2",
"consul-version": "1.17.0",
},
ServiceMeta: map[string]string{},
Address: testConsul.Config.Bind,
Expand Down
10 changes: 8 additions & 2 deletions docs/templating-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ Query [Consul][consul] for [connect][connect]-capable services based on their
health.

```golang
{{ connect "<TAG>.<NAME>@<DATACENTER>~<NEAR>|<FILTER>" }}
{{ connect "<TAG>.<NAME>?<QUERY>@<DATACENTER>~<NEAR>|<FILTER>" }}
```

Syntax is exactly the same as for the [service](#service) function below.
Expand Down Expand Up @@ -663,11 +663,17 @@ to separate files from a template.
Query [Consul][consul] for services based on their health.

```golang
{{ service "<TAG>.<NAME>@<DATACENTER>~<NEAR>|<FILTER>" }}
{{ service "<TAG>.<NAME>?<QUERY>@<DATACENTER>~<NEAR>|<FILTER>" }}
```

The `<TAG>` attribute is optional; if omitted, all nodes will be queried.

The `<QUERY>` attribute is optional; if omitted, the `default` Consul namespace, `default` partition will be queried. `<QUERY>` can be used to set the Consul [namespace](https://developer.hashicorp.com/consul/api-docs/health#ns-2), partition, or [peer](https://developer.hashicorp.com/consul/api-docs/health#peer). `<QUERY>` accepts a url query-parameter format, e.g.:

```golang
{{ service "service-name?ns=namespace-name&peer=peer-name&partition=partition-name" }}
```

The `<DATACENTER>` attribute is optional; if omitted, the local datacenter is
used.

Expand Down

0 comments on commit d0f9f1a

Please sign in to comment.