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..0ce4096e1 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)", + "node?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 6a2585020..7831684b1 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() { @@ -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, }, 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 { diff --git a/dependency/dependency.go b/dependency/dependency.go index cc89f2622..e96cda3c4 100644 --- a/dependency/dependency.go +++ b/dependency/dependency.go @@ -4,6 +4,7 @@ package dependency import ( + "fmt" "net/url" "regexp" "sort" @@ -16,13 +17,13 @@ import ( const ( dcRe = `(@(?P[[:word:]\.\-\_]+))?` - keyRe = `/?(?P[^@]+)` + keyRe = `/?(?P[^@\?]+)` filterRe = `(\|(?P[[:word:]\,]+))?` serviceNameRe = `(?P[[:word:]\-\_]+)` queryRe = `(\?(?P[[:word:]\-\_\=\&]+))?` nodeNameRe = `(?P[[:word:]\.\-\_]+)` nearRe = `(~(?P[[:word:]\.\-\_]+))?` - prefixRe = `/?(?P[^@]+)` + prefixRe = `/?(?P[^@\?]+)` tagRe = `((?P[[:word:]=:\.\-\_]+)\.)?` regionRe = `(@(?P[[:word:]\.\-\_]+))?` nvPathRe = `/?(?P[^@]+)` @@ -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{ diff --git a/dependency/kv_get.go b/dependency/kv_get.go index eb56a25f1..938ef8ba3 100644 --- a/dependency/kv_get.go +++ b/dependency/kv_get.go @@ -17,7 +17,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 +27,8 @@ type KVGetQuery struct { dc string key string blockOnNil bool + namespace string + partition string } // NewKVGetQuery parses a string into a dependency. @@ -36,10 +38,16 @@ func NewKVGetQuery(s string) (*KVGetQuery, error) { } m := regexpMatch(KVGetQueryRe, s) + 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 +60,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/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", 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 6357b4a05..459b8e203 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 +{{ key "key?ns=namespace-name&partition=partition-name" }} ``` The `` attribute is optional; if omitted, the local datacenter is @@ -342,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 @@ -352,7 +366,8 @@ For example: ```golang {{ range ls "service/redis" }} -{{ .Key }}:{{ .Value }}{{ end }} + {{ .Key }}:{{ .Value }} +{{ end }} ``` renders @@ -384,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. @@ -426,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 @@ -762,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