From bccf62cdceb2571dd5d8da45ead12871eab17ef0 Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Tue, 21 Nov 2023 09:26:38 -0500 Subject: [PATCH 1/7] [NET-1484] Support for namespaces, partitions and peers in consul endpoints --- dependency/dependency.go | 29 +++++++++++++++++++++++++++++ dependency/health_service.go | 23 +++-------------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/dependency/dependency.go b/dependency/dependency.go index cc89f2622..3aeaef996 100644 --- a/dependency/dependency.go +++ b/dependency/dependency.go @@ -4,6 +4,7 @@ package dependency import ( + "fmt" "net/url" "regexp" "sort" @@ -150,6 +151,34 @@ func (q *QueryOptions) ToConsulOpts() *consulapi.QueryOptions { } } +// GetConsulQueryOpts parses optional consul query params into key pairs. +// supports namespace, peer and partition params +func GetConsulQueryOpts(queryMap map[string]string, endpointLabel string) (url.Values, error) { + queryParams := url.Values{} + + if queryRaw := queryMap["query"]; queryRaw != "" { + var err error + queryParams, err = url.ParseQuery(queryRaw) + if err != nil { + return nil, fmt.Errorf( + "%s: invalid query: %q: %s", endpointLabel, queryRaw, err) + } + // Validate keys. + for key := range queryParams { + switch key { + case QueryNamespace, + QueryPeer, + QueryPartition: + default: + return nil, + fmt.Errorf("%s: invalid query parameter key %q in query %q: supported keys: %s,%s,%s", endpointLabel, key, queryRaw, QueryNamespace, QueryPeer, QueryPartition) + } + } + } + + return queryParams, nil +} + func (q *QueryOptions) ToNomadOpts() *nomadapi.QueryOptions { var params map[string]string if q.Choose != "" { diff --git a/dependency/health_service.go b/dependency/health_service.go index d9462a09f..2afe1b7e1 100644 --- a/dependency/health_service.go +++ b/dependency/health_service.go @@ -117,26 +117,9 @@ 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) - } - } + queryParams, err := GetConsulQueryOpts(m, "health.service") + if err != nil { + return nil, err } return &HealthServiceQuery{ From c49c567edd42efec96259cccfdf1ca4efd862fe4 Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Tue, 21 Nov 2023 11:22:54 -0500 Subject: [PATCH 2/7] kv get endpoint --- dependency/dependency.go | 2 +- dependency/kv_get.go | 22 ++++++++++--- dependency/kv_get_test.go | 61 +++++++++++++++++++++++++++++++++++++ docs/templating-language.md | 8 ++++- 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/dependency/dependency.go b/dependency/dependency.go index 3aeaef996..d48c94983 100644 --- a/dependency/dependency.go +++ b/dependency/dependency.go @@ -17,7 +17,7 @@ import ( const ( dcRe = `(@(?P[[:word:]\.\-\_]+))?` - keyRe = `/?(?P[^@]+)` + keyRe = `/?(?P[^@\?]+)` filterRe = `(\|(?P[[:word:]\,]+))?` serviceNameRe = `(?P[[:word:]\-\_]+)` queryRe = `(\?(?P[[:word:]\-\_\=\&]+))?` diff --git a/dependency/kv_get.go b/dependency/kv_get.go index eb56a25f1..3e951d93d 100644 --- a/dependency/kv_get.go +++ b/dependency/kv_get.go @@ -9,6 +9,7 @@ import ( "net/url" "regexp" + "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" ) @@ -17,7 +18,7 @@ var ( _ Dependency = (*KVGetQuery)(nil) // KVGetQueryRe is the regular expression to use. - KVGetQueryRe = regexp.MustCompile(`\A` + keyRe + dcRe + `\z`) + KVGetQueryRe = regexp.MustCompile(`\A` + keyRe + queryRe + dcRe + `\z`) ) // KVGetQuery queries the KV store for a single key. @@ -27,6 +28,8 @@ type KVGetQuery struct { dc string key string blockOnNil bool + namespace string + partition string } // NewKVGetQuery parses a string into a dependency. @@ -36,10 +39,17 @@ func NewKVGetQuery(s string) (*KVGetQuery, error) { } m := regexpMatch(KVGetQueryRe, s) + spew.Dump(m["key"]) + queryParams, err := GetConsulQueryOpts(m, "kv.get") + if err != nil { + return nil, err + } return &KVGetQuery{ - stopCh: make(chan struct{}, 1), - dc: m["dc"], - key: m["key"], + stopCh: make(chan struct{}, 1), + dc: m["dc"], + key: m["key"], + namespace: queryParams.Get(QueryNamespace), + partition: queryParams.Get(QueryPartition), }, nil } @@ -52,7 +62,9 @@ func (d *KVGetQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, } opts = opts.Merge(&QueryOptions{ - Datacenter: d.dc, + Datacenter: d.dc, + ConsulPartition: d.partition, + ConsulNamespace: d.namespace, }) log.Printf("[TRACE] %s: GET %s", d, &url.URL{ diff --git a/dependency/kv_get_test.go b/dependency/kv_get_test.go index 2429e3c5f..eff836d73 100644 --- a/dependency/kv_get_test.go +++ b/dependency/kv_get_test.go @@ -31,6 +31,18 @@ func TestNewKVGetQuery(t *testing.T) { nil, true, }, + { + "query_only", + "?ns=foo", + nil, + true, + }, + { + "invalid query param (unsupported key)", + "key?unsupported=foo", + nil, + true, + }, { "key", "key", @@ -48,6 +60,55 @@ func TestNewKVGetQuery(t *testing.T) { }, false, }, + { + "partition", + "key?partition=foo", + &KVGetQuery{ + key: "key", + partition: "foo", + }, + false, + }, + { + "namespace", + "key?ns=foo", + &KVGetQuery{ + key: "key", + namespace: "foo", + }, + false, + }, + { + "namespace_and_partition", + "key?ns=foo&partition=bar", + &KVGetQuery{ + key: "key", + namespace: "foo", + partition: "bar", + }, + false, + }, + { + "namespace_and_partition_and_dc", + "key?ns=foo&partition=bar@dc1", + &KVGetQuery{ + key: "key", + namespace: "foo", + partition: "bar", + dc: "dc1", + }, + false, + }, + { + "empty_query", + "key?ns=&partition=", + &KVGetQuery{ + key: "key", + namespace: "", + partition: "", + }, + false, + }, { "dots", "key.with.dots", diff --git a/docs/templating-language.md b/docs/templating-language.md index 6357b4a05..a371b9b9e 100644 --- a/docs/templating-language.md +++ b/docs/templating-language.md @@ -261,7 +261,13 @@ exist, Consul Template will block rendering until the key is present. To avoid blocking, use [`keyOrDefault`](#keyordefault) or [`keyExists`](#keyexists). ```golang -{{ key "@" }} +{{ key "?@" }} +``` + +The `` attribute is optional; if omitted, the `default` Consul namespace, `default` partition will be queried. `` can be used to set the Consul [namespace](https://developer.hashicorp.com/consul/api-docs/health#ns-2) or partition. `` accepts a url query-parameter format, e.g.: + +```golang +{{ service "key?ns=namespace-name&partition=partition-name" }} ``` The `` attribute is optional; if omitted, the local datacenter is From 522ad1e2e39c3b642a1f0a6c93e533bceb0e36f0 Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Tue, 21 Nov 2023 12:08:33 -0500 Subject: [PATCH 3/7] kv list endpoint --- dependency/dependency.go | 2 +- dependency/kv_get.go | 2 -- dependency/kv_list.go | 25 ++++++++++----- dependency/kv_list_test.go | 61 +++++++++++++++++++++++++++++++++++++ docs/templating-language.md | 15 +++++++-- 5 files changed, 92 insertions(+), 13 deletions(-) diff --git a/dependency/dependency.go b/dependency/dependency.go index d48c94983..e96cda3c4 100644 --- a/dependency/dependency.go +++ b/dependency/dependency.go @@ -23,7 +23,7 @@ const ( queryRe = `(\?(?P[[:word:]\-\_\=\&]+))?` nodeNameRe = `(?P[[:word:]\.\-\_]+)` nearRe = `(~(?P[[:word:]\.\-\_]+))?` - prefixRe = `/?(?P[^@]+)` + prefixRe = `/?(?P[^@\?]+)` tagRe = `((?P[[:word:]=:\.\-\_]+)\.)?` regionRe = `(@(?P[[:word:]\.\-\_]+))?` nvPathRe = `/?(?P[^@]+)` diff --git a/dependency/kv_get.go b/dependency/kv_get.go index 3e951d93d..938ef8ba3 100644 --- a/dependency/kv_get.go +++ b/dependency/kv_get.go @@ -9,7 +9,6 @@ import ( "net/url" "regexp" - "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" ) @@ -39,7 +38,6 @@ func NewKVGetQuery(s string) (*KVGetQuery, error) { } m := regexpMatch(KVGetQueryRe, s) - spew.Dump(m["key"]) queryParams, err := GetConsulQueryOpts(m, "kv.get") if err != nil { return nil, err diff --git a/dependency/kv_list.go b/dependency/kv_list.go index 8c19a63c6..91de6efbc 100644 --- a/dependency/kv_list.go +++ b/dependency/kv_list.go @@ -19,7 +19,7 @@ var ( _ Dependency = (*KVListQuery)(nil) // KVListQueryRe is the regular expression to use. - KVListQueryRe = regexp.MustCompile(`\A` + prefixRe + dcRe + `\z`) + KVListQueryRe = regexp.MustCompile(`\A` + prefixRe + queryRe + dcRe + `\z`) ) func init() { @@ -44,8 +44,10 @@ type KeyPair struct { type KVListQuery struct { stopCh chan struct{} - dc string - prefix string + dc string + prefix string + namespace string + partition string } // NewKVListQuery parses a string into a dependency. @@ -55,10 +57,17 @@ func NewKVListQuery(s string) (*KVListQuery, error) { } m := regexpMatch(KVListQueryRe, s) + queryParams, err := GetConsulQueryOpts(m, "kv.list") + if err != nil { + return nil, err + } + return &KVListQuery{ - stopCh: make(chan struct{}, 1), - dc: m["dc"], - prefix: m["prefix"], + stopCh: make(chan struct{}, 1), + dc: m["dc"], + prefix: m["prefix"], + namespace: queryParams.Get(QueryNamespace), + partition: queryParams.Get(QueryPartition), }, nil } @@ -71,7 +80,9 @@ func (d *KVListQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{} } opts = opts.Merge(&QueryOptions{ - Datacenter: d.dc, + Datacenter: d.dc, + ConsulPartition: d.partition, + ConsulNamespace: d.namespace, }) log.Printf("[TRACE] %s: GET %s", d, &url.URL{ diff --git a/dependency/kv_list_test.go b/dependency/kv_list_test.go index 50be5dd1f..d6201496c 100644 --- a/dependency/kv_list_test.go +++ b/dependency/kv_list_test.go @@ -30,6 +30,18 @@ func TestNewKVListQuery(t *testing.T) { nil, true, }, + { + "query_only", + "?ns=foo", + nil, + true, + }, + { + "invalid query param (unsupported key)", + "prefix?unsupported=foo", + nil, + true, + }, { "prefix", "prefix", @@ -47,6 +59,55 @@ func TestNewKVListQuery(t *testing.T) { }, false, }, + { + "partition", + "prefix?partition=foo", + &KVListQuery{ + prefix: "prefix", + partition: "foo", + }, + false, + }, + { + "namespace", + "prefix?ns=foo", + &KVListQuery{ + prefix: "prefix", + namespace: "foo", + }, + false, + }, + { + "namespace_and_partition", + "prefix?ns=foo&partition=bar", + &KVListQuery{ + prefix: "prefix", + namespace: "foo", + partition: "bar", + }, + false, + }, + { + "namespace_and_partition_and_dc", + "prefix?ns=foo&partition=bar@dc1", + &KVListQuery{ + prefix: "prefix", + namespace: "foo", + partition: "bar", + dc: "dc1", + }, + false, + }, + { + "empty_query", + "prefix?ns=&partition=", + &KVListQuery{ + prefix: "prefix", + namespace: "", + partition: "", + }, + false, + }, { "dots", "prefix.with.dots", diff --git a/docs/templating-language.md b/docs/templating-language.md index a371b9b9e..ed2ab251b 100644 --- a/docs/templating-language.md +++ b/docs/templating-language.md @@ -267,7 +267,7 @@ blocking, use [`keyOrDefault`](#keyordefault) or [`keyExists`](#keyexists). The `` attribute is optional; if omitted, the `default` Consul namespace, `default` partition will be queried. `` can be used to set the Consul [namespace](https://developer.hashicorp.com/consul/api-docs/health#ns-2) or partition. `` accepts a url query-parameter format, e.g.: ```golang -{{ service "key?ns=namespace-name&partition=partition-name" }} +{{ key "key?ns=namespace-name&partition=partition-name" }} ``` The `` attribute is optional; if omitted, the local datacenter is @@ -348,7 +348,15 @@ instead. Query [Consul][consul] for all top-level kv pairs at the given key path. ```golang -{{ ls "@" }} +{{ ls "?@" }} +``` + +The `` attribute is optional; if omitted, the `default` Consul namespace, `default` partition will be queried. `` can be used to set the Consul [namespace](https://developer.hashicorp.com/consul/api-docs/health#ns-2) or partition. `` accepts a url query-parameter format, e.g.: + +```golang +{{ range ls "service/redis?ns=namespace-name&partition=partition-name" }} + {{ .Key }}:{{ .Value }} +{{ end }} ``` The `` attribute is optional; if omitted, the local datacenter is @@ -358,7 +366,8 @@ For example: ```golang {{ range ls "service/redis" }} -{{ .Key }}:{{ .Value }}{{ end }} + {{ .Key }}:{{ .Value }} +{{ end }} ``` renders From 295243e63a380393480a6c289af460bd330a0cf8 Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Tue, 21 Nov 2023 12:56:35 -0500 Subject: [PATCH 4/7] kv keys endpoint --- dependency/kv_keys.go | 25 +++++++++++----- dependency/kv_keys_test.go | 61 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/dependency/kv_keys.go b/dependency/kv_keys.go index 32ae98100..cd20a6ef6 100644 --- a/dependency/kv_keys.go +++ b/dependency/kv_keys.go @@ -18,15 +18,17 @@ var ( _ Dependency = (*KVKeysQuery)(nil) // KVKeysQueryRe is the regular expression to use. - KVKeysQueryRe = regexp.MustCompile(`\A` + prefixRe + dcRe + `\z`) + KVKeysQueryRe = regexp.MustCompile(`\A` + prefixRe + queryRe + dcRe + `\z`) ) // KVKeysQuery queries the KV store for a single key. type KVKeysQuery struct { stopCh chan struct{} - dc string - prefix string + dc string + prefix string + namespace string + partition string } // NewKVKeysQuery parses a string into a dependency. @@ -36,10 +38,17 @@ func NewKVKeysQuery(s string) (*KVKeysQuery, error) { } m := regexpMatch(KVKeysQueryRe, s) + queryParams, err := GetConsulQueryOpts(m, "kv.keys") + if err != nil { + return nil, err + } + return &KVKeysQuery{ - stopCh: make(chan struct{}, 1), - dc: m["dc"], - prefix: m["prefix"], + stopCh: make(chan struct{}, 1), + dc: m["dc"], + prefix: m["prefix"], + namespace: queryParams.Get(QueryNamespace), + partition: queryParams.Get(QueryPartition), }, nil } @@ -52,7 +61,9 @@ func (d *KVKeysQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{} } opts = opts.Merge(&QueryOptions{ - Datacenter: d.dc, + Datacenter: d.dc, + ConsulPartition: d.partition, + ConsulNamespace: d.namespace, }) log.Printf("[TRACE] %s: GET %s", d, &url.URL{ diff --git a/dependency/kv_keys_test.go b/dependency/kv_keys_test.go index 4c02dd669..6e018665f 100644 --- a/dependency/kv_keys_test.go +++ b/dependency/kv_keys_test.go @@ -30,6 +30,18 @@ func TestNewKVKeysQuery(t *testing.T) { nil, true, }, + { + "query_only", + "?ns=foo", + nil, + true, + }, + { + "invalid query param (unsupported key)", + "prefix?unsupported=foo", + nil, + true, + }, { "prefix", "prefix", @@ -47,6 +59,55 @@ func TestNewKVKeysQuery(t *testing.T) { }, false, }, + { + "partition", + "prefix?partition=foo", + &KVKeysQuery{ + prefix: "prefix", + partition: "foo", + }, + false, + }, + { + "namespace", + "prefix?ns=foo", + &KVKeysQuery{ + prefix: "prefix", + namespace: "foo", + }, + false, + }, + { + "namespace_and_partition", + "prefix?ns=foo&partition=bar", + &KVKeysQuery{ + prefix: "prefix", + namespace: "foo", + partition: "bar", + }, + false, + }, + { + "namespace_and_partition_and_dc", + "prefix?ns=foo&partition=bar@dc1", + &KVKeysQuery{ + prefix: "prefix", + namespace: "foo", + partition: "bar", + dc: "dc1", + }, + false, + }, + { + "empty_query", + "prefix?ns=&partition=", + &KVKeysQuery{ + prefix: "prefix", + namespace: "", + partition: "", + }, + false, + }, { "dots", "prefix.with.dots", From 3e9f63f758b7a3f38e674b417dbddf26f457215a Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Tue, 21 Nov 2023 13:32:19 -0500 Subject: [PATCH 5/7] catalog services endpoint --- dependency/catalog_service.go | 2 +- dependency/catalog_services.go | 21 +++++++++++---- dependency/catalog_services_test.go | 41 +++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/dependency/catalog_service.go b/dependency/catalog_service.go index 6a2585020..8a875f863 100644 --- a/dependency/catalog_service.go +++ b/dependency/catalog_service.go @@ -18,7 +18,7 @@ var ( _ Dependency = (*CatalogServiceQuery)(nil) // CatalogServiceQueryRe is the regular expression to use. - CatalogServiceQueryRe = regexp.MustCompile(`\A` + tagRe + serviceNameRe + dcRe + nearRe + `\z`) + CatalogServiceQueryRe = regexp.MustCompile(`\A` + tagRe + serviceNameRe + queryRe + dcRe + nearRe + `\z`) ) func init() { diff --git a/dependency/catalog_services.go b/dependency/catalog_services.go index 5a89028e7..af62e2c1c 100644 --- a/dependency/catalog_services.go +++ b/dependency/catalog_services.go @@ -19,7 +19,7 @@ var ( _ Dependency = (*CatalogServicesQuery)(nil) // CatalogServicesQueryRe is the regular expression to use for CatalogNodesQuery. - CatalogServicesQueryRe = regexp.MustCompile(`\A` + dcRe + `\z`) + CatalogServicesQueryRe = regexp.MustCompile(`\A` + queryRe + dcRe + `\z`) ) func init() { @@ -37,7 +37,9 @@ type CatalogSnippet struct { type CatalogServicesQuery struct { stopCh chan struct{} - dc string + dc string + namespace string + partition string } // NewCatalogServicesQuery parses a string of the format @dc. @@ -47,9 +49,16 @@ func NewCatalogServicesQuery(s string) (*CatalogServicesQuery, error) { } m := regexpMatch(CatalogServicesQueryRe, s) + queryParams, err := GetConsulQueryOpts(m, "catalog.services") + if err != nil { + return nil, err + } + return &CatalogServicesQuery{ - stopCh: make(chan struct{}, 1), - dc: m["dc"], + stopCh: make(chan struct{}, 1), + dc: m["dc"], + namespace: queryParams.Get(QueryNamespace), + partition: queryParams.Get(QueryPartition), }, nil } @@ -63,7 +72,9 @@ func (d *CatalogServicesQuery) Fetch(clients *ClientSet, opts *QueryOptions) (in } opts = opts.Merge(&QueryOptions{ - Datacenter: d.dc, + Datacenter: d.dc, + ConsulPartition: d.partition, + ConsulNamespace: d.namespace, }) log.Printf("[TRACE] %s: GET %s", d, &url.URL{ diff --git a/dependency/catalog_services_test.go b/dependency/catalog_services_test.go index 76fc4cbc7..928601e8c 100644 --- a/dependency/catalog_services_test.go +++ b/dependency/catalog_services_test.go @@ -23,6 +23,12 @@ func TestNewCatalogServicesQuery(t *testing.T) { &CatalogServicesQuery{}, false, }, + { + "invalid query param (unsupported key)", + "?unsupported=foo", + nil, + true, + }, { "node", "node", @@ -37,6 +43,41 @@ func TestNewCatalogServicesQuery(t *testing.T) { }, false, }, + { + "namespace", + "?ns=foo", + &CatalogServicesQuery{ + namespace: "foo", + }, + false, + }, + { + "partition", + "?partition=foo", + &CatalogServicesQuery{ + partition: "foo", + }, + false, + }, + { + "partition_and_namespace", + "?ns=foo&partition=bar", + &CatalogServicesQuery{ + namespace: "foo", + partition: "bar", + }, + false, + }, + { + "partition_and_namespace_and_dc", + "?ns=foo&partition=bar@dc1", + &CatalogServicesQuery{ + namespace: "foo", + partition: "bar", + dc: "dc1", + }, + false, + }, } for i, tc := range cases { From c639cbb19a49ba74c2b26bdd561661b479cd51a1 Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Tue, 21 Nov 2023 14:45:18 -0500 Subject: [PATCH 6/7] catalog endpoints --- dependency/catalog_node.go | 25 ++++++++---- dependency/catalog_node_test.go | 23 +++++++++++ dependency/catalog_nodes.go | 27 +++++++++---- dependency/catalog_nodes_test.go | 61 ++++++++++++++++++++++++++++++ dependency/catalog_service.go | 33 ++++++++++------ dependency/catalog_service_test.go | 45 +++++++++++++++++++--- 6 files changed, 182 insertions(+), 32 deletions(-) diff --git a/dependency/catalog_node.go b/dependency/catalog_node.go index f3ab96cbd..50fcd57fd 100644 --- a/dependency/catalog_node.go +++ b/dependency/catalog_node.go @@ -19,7 +19,7 @@ var ( _ Dependency = (*CatalogNodeQuery)(nil) // CatalogNodeQueryRe is the regular expression to use. - CatalogNodeQueryRe = regexp.MustCompile(`\A` + nodeNameRe + dcRe + `\z`) + CatalogNodeQueryRe = regexp.MustCompile(`\A` + nodeNameRe + queryRe + dcRe + `\z`) ) func init() { @@ -31,8 +31,10 @@ func init() { type CatalogNodeQuery struct { stopCh chan struct{} - dc string - name string + dc string + name string + namespace string + partition string } // CatalogNode is a wrapper around the node and its services. @@ -60,10 +62,17 @@ func NewCatalogNodeQuery(s string) (*CatalogNodeQuery, error) { } m := regexpMatch(CatalogNodeQueryRe, s) + queryParams, err := GetConsulQueryOpts(m, "catalog.node") + if err != nil { + return nil, err + } + return &CatalogNodeQuery{ - dc: m["dc"], - name: m["name"], - stopCh: make(chan struct{}, 1), + dc: m["dc"], + name: m["name"], + stopCh: make(chan struct{}, 1), + namespace: queryParams.Get(QueryNamespace), + partition: queryParams.Get(QueryPartition), }, nil } @@ -77,7 +86,9 @@ func (d *CatalogNodeQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interf } opts = opts.Merge(&QueryOptions{ - Datacenter: d.dc, + Datacenter: d.dc, + ConsulPartition: d.partition, + ConsulNamespace: d.namespace, }) // Grab the name diff --git a/dependency/catalog_node_test.go b/dependency/catalog_node_test.go index be9862df0..e87b18962 100644 --- a/dependency/catalog_node_test.go +++ b/dependency/catalog_node_test.go @@ -23,6 +23,12 @@ func TestNewCatalogNodeQuery(t *testing.T) { &CatalogNodeQuery{}, false, }, + { + "invalid query param (unsupported key)", + "key?unsupported=foo", + nil, + true, + }, { "bad", "!4d", @@ -35,6 +41,12 @@ func TestNewCatalogNodeQuery(t *testing.T) { nil, true, }, + { + "query_only", + "?ns=foo", + nil, + true, + }, { "node", "node", @@ -52,6 +64,17 @@ func TestNewCatalogNodeQuery(t *testing.T) { }, false, }, + { + "every_option", + "node?ns=foo&partition=bar@dc1", + &CatalogNodeQuery{ + name: "node", + dc: "dc1", + namespace: "foo", + partition: "bar", + }, + false, + }, { "periods", "node.bar.com@dc1", diff --git a/dependency/catalog_nodes.go b/dependency/catalog_nodes.go index ebc45b9f6..ea0f062fb 100644 --- a/dependency/catalog_nodes.go +++ b/dependency/catalog_nodes.go @@ -19,7 +19,7 @@ var ( _ Dependency = (*CatalogNodesQuery)(nil) // CatalogNodesQueryRe is the regular expression to use. - CatalogNodesQueryRe = regexp.MustCompile(`\A` + dcRe + nearRe + `\z`) + CatalogNodesQueryRe = regexp.MustCompile(`\A` + queryRe + dcRe + nearRe + `\z`) ) func init() { @@ -40,8 +40,10 @@ type Node struct { type CatalogNodesQuery struct { stopCh chan struct{} - dc string - near string + dc string + near string + namespace string + partition string } // NewCatalogNodesQuery parses the given string into a dependency. If the name is @@ -52,10 +54,17 @@ func NewCatalogNodesQuery(s string) (*CatalogNodesQuery, error) { } m := regexpMatch(CatalogNodesQueryRe, s) + queryParams, err := GetConsulQueryOpts(m, "catalog.nodes") + if err != nil { + return nil, err + } + return &CatalogNodesQuery{ - dc: m["dc"], - near: m["near"], - stopCh: make(chan struct{}, 1), + dc: m["dc"], + near: m["near"], + stopCh: make(chan struct{}, 1), + namespace: queryParams.Get(QueryNamespace), + partition: queryParams.Get(QueryPartition), }, nil } @@ -69,8 +78,10 @@ func (d *CatalogNodesQuery) Fetch(clients *ClientSet, opts *QueryOptions) (inter } opts = opts.Merge(&QueryOptions{ - Datacenter: d.dc, - Near: d.near, + Datacenter: d.dc, + Near: d.near, + ConsulPartition: d.partition, + ConsulNamespace: d.namespace, }) log.Printf("[TRACE] %s: GET %s", d, &url.URL{ diff --git a/dependency/catalog_nodes_test.go b/dependency/catalog_nodes_test.go index 6a18f4988..96b3c18a9 100644 --- a/dependency/catalog_nodes_test.go +++ b/dependency/catalog_nodes_test.go @@ -23,6 +23,12 @@ func TestNewCatalogNodesQuery(t *testing.T) { &CatalogNodesQuery{}, false, }, + { + "invalid query param (unsupported key)", + "key?unsupported=foo", + nil, + true, + }, { "node", "node", @@ -37,6 +43,41 @@ func TestNewCatalogNodesQuery(t *testing.T) { }, false, }, + { + "namespace", + "?ns=foo", + &CatalogNodesQuery{ + namespace: "foo", + }, + false, + }, + { + "partition", + "?partition=foo", + &CatalogNodesQuery{ + partition: "foo", + }, + false, + }, + { + "namespace_and_partition", + "?ns=foo&partition=bar", + &CatalogNodesQuery{ + namespace: "foo", + partition: "bar", + }, + false, + }, + { + "namespace_and_partition_and_near", + "?ns=foo&partition=bar~node1", + &CatalogNodesQuery{ + namespace: "foo", + partition: "bar", + near: "node1", + }, + false, + }, { "near", "~node1", @@ -54,6 +95,26 @@ func TestNewCatalogNodesQuery(t *testing.T) { }, false, }, + { + "query_near", + "?ns=foo~node1", + &CatalogNodesQuery{ + namespace: "foo", + near: "node1", + }, + false, + }, + { + "every_option", + "?ns=foo&partition=bar@dc1~node1", + &CatalogNodesQuery{ + dc: "dc1", + near: "node1", + partition: "bar", + namespace: "foo", + }, + false, + }, } for i, tc := range cases { diff --git a/dependency/catalog_service.go b/dependency/catalog_service.go index 8a875f863..7831684b1 100644 --- a/dependency/catalog_service.go +++ b/dependency/catalog_service.go @@ -46,10 +46,12 @@ type CatalogService struct { type CatalogServiceQuery struct { stopCh chan struct{} - dc string - name string - near string - tag string + dc string + name string + near string + tag string + namespace string + partition string } // NewCatalogServiceQuery parses a string into a CatalogServiceQuery. @@ -59,12 +61,19 @@ func NewCatalogServiceQuery(s string) (*CatalogServiceQuery, error) { } m := regexpMatch(CatalogServiceQueryRe, s) + queryParams, err := GetConsulQueryOpts(m, "catalog.service") + if err != nil { + return nil, err + } + return &CatalogServiceQuery{ - stopCh: make(chan struct{}, 1), - dc: m["dc"], - name: m["name"], - near: m["near"], - tag: m["tag"], + stopCh: make(chan struct{}, 1), + dc: m["dc"], + name: m["name"], + near: m["near"], + tag: m["tag"], + namespace: queryParams.Get(QueryNamespace), + partition: queryParams.Get(QueryPartition), }, nil } @@ -78,8 +87,10 @@ func (d *CatalogServiceQuery) Fetch(clients *ClientSet, opts *QueryOptions) (int } opts = opts.Merge(&QueryOptions{ - Datacenter: d.dc, - Near: d.near, + Datacenter: d.dc, + Near: d.near, + ConsulPartition: d.partition, + ConsulNamespace: d.namespace, }) u := &url.URL{ diff --git a/dependency/catalog_service_test.go b/dependency/catalog_service_test.go index 85c07a744..262eb27bf 100644 --- a/dependency/catalog_service_test.go +++ b/dependency/catalog_service_test.go @@ -29,6 +29,18 @@ func TestNewCatalogServiceQuery(t *testing.T) { nil, true, }, + { + "query_only", + "?ns=foo", + nil, + true, + }, + { + "invalid query param (unsupported key)", + "name?unsupported=foo", + nil, + true, + }, { "near_only", "~near", @@ -58,6 +70,15 @@ func TestNewCatalogServiceQuery(t *testing.T) { }, false, }, + { + "name_query", + "name?ns=foo", + &CatalogServiceQuery{ + name: "name", + namespace: "foo", + }, + false, + }, { "name_dc_near", "name@dc1~near", @@ -68,6 +89,16 @@ func TestNewCatalogServiceQuery(t *testing.T) { }, false, }, + { + "name_query_near", + "name?ns=foo~near", + &CatalogServiceQuery{ + name: "name", + near: "near", + namespace: "foo", + }, + false, + }, { "name_near", "name~near", @@ -107,13 +138,15 @@ func TestNewCatalogServiceQuery(t *testing.T) { false, }, { - "tag_name_dc_near", - "tag.name@dc~near", + "every_option", + "tag.name?ns=foo&partition=bar@dc~near", &CatalogServiceQuery{ - dc: "dc", - name: "name", - near: "near", - tag: "tag", + dc: "dc", + name: "name", + near: "near", + tag: "tag", + namespace: "foo", + partition: "bar", }, false, }, From d95a3881c47ddd732ab9ff75a18af12e55afc030 Mon Sep 17 00:00:00 2001 From: Ronald Ekambi Date: Tue, 21 Nov 2023 15:27:42 -0500 Subject: [PATCH 7/7] docs --- dependency/catalog_node_test.go | 2 +- docs/templating-language.md | 28 +++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/dependency/catalog_node_test.go b/dependency/catalog_node_test.go index e87b18962..0ce4096e1 100644 --- a/dependency/catalog_node_test.go +++ b/dependency/catalog_node_test.go @@ -25,7 +25,7 @@ func TestNewCatalogNodeQuery(t *testing.T) { }, { "invalid query param (unsupported key)", - "key?unsupported=foo", + "node?unsupported=foo", nil, true, }, diff --git a/docs/templating-language.md b/docs/templating-language.md index ed2ab251b..459b8e203 100644 --- a/docs/templating-language.md +++ b/docs/templating-language.md @@ -399,11 +399,19 @@ To learn how [`safeLs`](#safels) was born see [CT-1131](https://github.com/hashi Query [Consul][consul] for a node in the catalog. ```golang -{{node "@"}} +{{node "?@"}} ``` The `` attribute is optional; if omitted, the local agent node is used. +The `` attribute is optional; if omitted, the `default` Consul namespace, `default` partition will be queried. `` can be used to set the Consul [namespace](https://developer.hashicorp.com/consul/api-docs/health#ns-2) or partition. `` accepts a url query-parameter format, e.g.: + +```golang +{{ with node "node?ns=default&partition=default" }} + {{ .Node.Address }} +{{ end }} +``` + The `` attribute is optional; if omitted, the local datacenter is used. @@ -441,7 +449,13 @@ To access map data such as `TaggedAddresses` or `Meta`, use Query [Consul][consul] for all nodes in the catalog. ```golang -{{ nodes "@~" }} +{{ nodes "?@~" }} +``` +The `` attribute is optional; if omitted, the `default` Consul namespace, `default` partition will be queried. `` can be used to set the Consul [namespace](https://developer.hashicorp.com/consul/api-docs/health#ns-2) or partition. `` accepts a url query-parameter format, e.g.: + +```golang +{{ range nodes "?ns=namespace&partition=partition" }} +{{ .Address }}{{ end }} ``` The `` attribute is optional; if omitted, the local datacenter is @@ -777,7 +791,15 @@ argument instead. Query [Consul][consul] for all services in the catalog. ```golang -{{ services "@" }} +{{ services "?@" }} +``` + +The `` attribute is optional; if omitted, the `default` Consul namespace, `default` partition will be queried. `` can be used to set the Consul [namespace](https://developer.hashicorp.com/consul/api-docs/health#ns-2) or partition. `` accepts a url query-parameter format, e.g.: + +```golang +{{ range services "?ns=default&partition=default" }} + {{ .Name }} +{{ end }} ``` The `` attribute is optional; if omitted, the local datacenter is