Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Consul namespaces/partitions/peering #1822

Merged
merged 2 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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