mirror of https://github.com/hashicorp/consul
connect: change router syntax for matching query parameters to resemble the syntax for matching paths and headers for consistency. (#6163)
This is a breaking change, but only in the context of the beta series.pull/6164/head
parent
880d149c19
commit
85cf2706e6
|
@ -3121,16 +3121,16 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
||||||
"path_prefix": "/foo",
|
"path_prefix": "/foo",
|
||||||
"query_param": [
|
"query_param": [
|
||||||
{
|
{
|
||||||
"name": "hack1"
|
"name": "hack1",
|
||||||
|
"present": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hack2",
|
"name": "hack2",
|
||||||
"value": "1"
|
"exact": "1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hack3",
|
"name": "hack3",
|
||||||
"value": "a.*z",
|
"regex": "a.*z"
|
||||||
"regex": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -3205,15 +3205,15 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
||||||
query_param = [
|
query_param = [
|
||||||
{
|
{
|
||||||
name = "hack1"
|
name = "hack1"
|
||||||
|
present = true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "hack2"
|
name = "hack2"
|
||||||
value = "1"
|
exact = "1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "hack3"
|
name = "hack3"
|
||||||
value = "a.*z"
|
regex = "a.*z"
|
||||||
regex = true
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -3287,15 +3287,15 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
||||||
QueryParam: []structs.ServiceRouteHTTPMatchQueryParam{
|
QueryParam: []structs.ServiceRouteHTTPMatchQueryParam{
|
||||||
{
|
{
|
||||||
Name: "hack1",
|
Name: "hack1",
|
||||||
|
Present: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "hack2",
|
Name: "hack2",
|
||||||
Value: "1",
|
Exact: "1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "hack3",
|
Name: "hack3",
|
||||||
Value: "a.*z",
|
Regex: "a.*z",
|
||||||
Regex: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -124,6 +124,20 @@ func (e *ServiceRouterConfigEntry) Validate() error {
|
||||||
if qm.Name == "" {
|
if qm.Name == "" {
|
||||||
return fmt.Errorf("Route[%d] QueryParam[%d] missing required Name field", i, j)
|
return fmt.Errorf("Route[%d] QueryParam[%d] missing required Name field", i, j)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qmParts := 0
|
||||||
|
if qm.Present {
|
||||||
|
qmParts++
|
||||||
|
}
|
||||||
|
if qm.Exact != "" {
|
||||||
|
qmParts++
|
||||||
|
}
|
||||||
|
if qm.Regex != "" {
|
||||||
|
qmParts++
|
||||||
|
}
|
||||||
|
if qmParts != 1 {
|
||||||
|
return fmt.Errorf("Route[%d] QueryParam[%d] should only contain one of Present, Exact, or Regex", i, j)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,8 +244,9 @@ type ServiceRouteHTTPMatchHeader struct {
|
||||||
|
|
||||||
type ServiceRouteHTTPMatchQueryParam struct {
|
type ServiceRouteHTTPMatchQueryParam struct {
|
||||||
Name string
|
Name string
|
||||||
Value string `json:",omitempty"`
|
Present bool `json:",omitempty"`
|
||||||
Regex bool `json:",omitempty"`
|
Exact string `json:",omitempty"`
|
||||||
|
Regex string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceRouteDestination describes how to proxy the actual matching request
|
// ServiceRouteDestination describes how to proxy the actual matching request
|
||||||
|
|
|
@ -960,11 +960,68 @@ func TestServiceRouterConfigEntry(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "route with no name query param",
|
name: "route with no name query param",
|
||||||
entry: makerouter(routeMatch(httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
entry: makerouter(routeMatch(httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
||||||
Value: "foo",
|
Exact: "foo",
|
||||||
}))),
|
}))),
|
||||||
validateErr: "missing required Name field",
|
validateErr: "missing required Name field",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "route with query param exact match",
|
||||||
|
entry: makerouter(routeMatch(httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
||||||
|
Name: "foo",
|
||||||
|
Exact: "bar",
|
||||||
|
}))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "route with query param regex match",
|
||||||
|
entry: makerouter(routeMatch(httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
||||||
|
Name: "foo",
|
||||||
|
Regex: "bar",
|
||||||
|
}))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "route with query param present match",
|
||||||
|
entry: makerouter(routeMatch(httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
||||||
|
Name: "foo",
|
||||||
|
Present: true,
|
||||||
|
}))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "route with query param exact and regex match",
|
||||||
|
entry: makerouter(routeMatch(httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
||||||
|
Name: "foo",
|
||||||
|
Exact: "bar",
|
||||||
|
Regex: "bar",
|
||||||
|
}))),
|
||||||
|
validateErr: "should only contain one of Present, Exact, or Regex",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "route with query param exact and present match",
|
||||||
|
entry: makerouter(routeMatch(httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
||||||
|
Name: "foo",
|
||||||
|
Exact: "bar",
|
||||||
|
Present: true,
|
||||||
|
}))),
|
||||||
|
validateErr: "should only contain one of Present, Exact, or Regex",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "route with query param regex and present match",
|
||||||
|
entry: makerouter(routeMatch(httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
||||||
|
Name: "foo",
|
||||||
|
Regex: "bar",
|
||||||
|
Present: true,
|
||||||
|
}))),
|
||||||
|
validateErr: "should only contain one of Present, Exact, or Regex",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "route with query param exact, regex, and present match",
|
||||||
|
entry: makerouter(routeMatch(httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
||||||
|
Name: "foo",
|
||||||
|
Exact: "bar",
|
||||||
|
Regex: "bar",
|
||||||
|
Present: true,
|
||||||
|
}))),
|
||||||
|
validateErr: "should only contain one of Present, Exact, or Regex",
|
||||||
|
},
|
||||||
////////////////
|
////////////////
|
||||||
{
|
{
|
||||||
name: "route with no match and prefix rewrite",
|
name: "route with no match and prefix rewrite",
|
||||||
|
@ -1033,7 +1090,7 @@ func TestServiceRouterConfigEntry(t *testing.T) {
|
||||||
entry: makerouter(ServiceRoute{
|
entry: makerouter(ServiceRoute{
|
||||||
Match: httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
Match: httpMatchParam(ServiceRouteHTTPMatchQueryParam{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Value: "bar",
|
Exact: "bar",
|
||||||
}),
|
}),
|
||||||
Destination: &ServiceRouteDestination{
|
Destination: &ServiceRouteDestination{
|
||||||
Service: "other",
|
Service: "other",
|
||||||
|
|
|
@ -171,15 +171,15 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
query_param = [
|
query_param = [
|
||||||
{
|
{
|
||||||
name = "hack1"
|
name = "hack1"
|
||||||
|
present = true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "hack2"
|
name = "hack2"
|
||||||
value = "1"
|
exact = "1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "hack3"
|
name = "hack3"
|
||||||
value = "a.*z"
|
regex = "a.*z"
|
||||||
regex = true
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -249,15 +249,15 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
QueryParam = [
|
QueryParam = [
|
||||||
{
|
{
|
||||||
Name = "hack1"
|
Name = "hack1"
|
||||||
|
Present = true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = "hack2"
|
Name = "hack2"
|
||||||
Value = "1"
|
Exact = "1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = "hack3"
|
Name = "hack3"
|
||||||
Value = "a.*z"
|
Regex = "a.*z"
|
||||||
Regex = true
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -327,15 +327,15 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
QueryParam: []ServiceRouteHTTPMatchQueryParam{
|
QueryParam: []ServiceRouteHTTPMatchQueryParam{
|
||||||
{
|
{
|
||||||
Name: "hack1",
|
Name: "hack1",
|
||||||
|
Present: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "hack2",
|
Name: "hack2",
|
||||||
Value: "1",
|
Exact: "1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "hack3",
|
Name: "hack3",
|
||||||
Value: "a.*z",
|
Regex: "a.*z",
|
||||||
Regex: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -257,8 +257,18 @@ func makeRouteMatchForDiscoveryRoute(discoveryRoute *structs.DiscoveryRoute, pro
|
||||||
for _, qm := range match.HTTP.QueryParam {
|
for _, qm := range match.HTTP.QueryParam {
|
||||||
eq := &envoyroute.QueryParameterMatcher{
|
eq := &envoyroute.QueryParameterMatcher{
|
||||||
Name: qm.Name,
|
Name: qm.Name,
|
||||||
Value: qm.Value,
|
}
|
||||||
Regex: makeBoolValue(qm.Regex),
|
|
||||||
|
switch {
|
||||||
|
case qm.Exact != "":
|
||||||
|
eq.Value = qm.Exact
|
||||||
|
case qm.Regex != "":
|
||||||
|
eq.Value = qm.Regex
|
||||||
|
eq.Regex = makeBoolValue(true)
|
||||||
|
case qm.Present:
|
||||||
|
eq.Value = ""
|
||||||
|
default:
|
||||||
|
continue // skip this impossible situation
|
||||||
}
|
}
|
||||||
|
|
||||||
em.QueryParameters = append(em.QueryParameters, eq)
|
em.QueryParameters = append(em.QueryParameters, eq)
|
||||||
|
|
|
@ -192,19 +192,25 @@ func TestRoutesFromSnapshot(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Match: httpMatchParam(structs.ServiceRouteHTTPMatchQueryParam{
|
Match: httpMatchParam(structs.ServiceRouteHTTPMatchQueryParam{
|
||||||
Name: "secretparam",
|
Name: "secretparam1",
|
||||||
Value: "exact",
|
Exact: "exact",
|
||||||
}),
|
}),
|
||||||
Destination: toService("prm-exact"),
|
Destination: toService("prm-exact"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Match: httpMatchParam(structs.ServiceRouteHTTPMatchQueryParam{
|
Match: httpMatchParam(structs.ServiceRouteHTTPMatchQueryParam{
|
||||||
Name: "secretparam",
|
Name: "secretparam2",
|
||||||
Value: "regex",
|
Regex: "regex",
|
||||||
Regex: true,
|
|
||||||
}),
|
}),
|
||||||
Destination: toService("prm-regex"),
|
Destination: toService("prm-regex"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Match: httpMatchParam(structs.ServiceRouteHTTPMatchQueryParam{
|
||||||
|
Name: "secretparam3",
|
||||||
|
Present: true,
|
||||||
|
}),
|
||||||
|
Destination: toService("prm-present"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Match: nil,
|
Match: nil,
|
||||||
Destination: toService("nil-match"),
|
Destination: toService("nil-match"),
|
||||||
|
|
|
@ -125,9 +125,8 @@
|
||||||
"prefix": "/",
|
"prefix": "/",
|
||||||
"queryParameters": [
|
"queryParameters": [
|
||||||
{
|
{
|
||||||
"name": "secretparam",
|
"name": "secretparam1",
|
||||||
"value": "exact",
|
"value": "exact"
|
||||||
"regex": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -140,7 +139,7 @@
|
||||||
"prefix": "/",
|
"prefix": "/",
|
||||||
"queryParameters": [
|
"queryParameters": [
|
||||||
{
|
{
|
||||||
"name": "secretparam",
|
"name": "secretparam2",
|
||||||
"value": "regex",
|
"value": "regex",
|
||||||
"regex": true
|
"regex": true
|
||||||
}
|
}
|
||||||
|
@ -150,6 +149,19 @@
|
||||||
"cluster": "prm-regex.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
"cluster": "prm-regex.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"prefix": "/",
|
||||||
|
"queryParameters": [
|
||||||
|
{
|
||||||
|
"name": "secretparam3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"cluster": "prm-present.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"prefix": "/"
|
"prefix": "/"
|
||||||
|
|
|
@ -50,8 +50,9 @@ type ServiceRouteHTTPMatchHeader struct {
|
||||||
|
|
||||||
type ServiceRouteHTTPMatchQueryParam struct {
|
type ServiceRouteHTTPMatchQueryParam struct {
|
||||||
Name string
|
Name string
|
||||||
Value string `json:",omitempty"`
|
Present bool `json:",omitempty"`
|
||||||
Regex bool `json:",omitempty"`
|
Exact string `json:",omitempty"`
|
||||||
|
Regex string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceRouteDestination struct {
|
type ServiceRouteDestination struct {
|
||||||
|
|
|
@ -200,7 +200,7 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
|
||||||
{Name: "x-debug", Exact: "1"},
|
{Name: "x-debug", Exact: "1"},
|
||||||
},
|
},
|
||||||
QueryParam: []ServiceRouteHTTPMatchQueryParam{
|
QueryParam: []ServiceRouteHTTPMatchQueryParam{
|
||||||
{Name: "debug", Value: "1"},
|
{Name: "debug", Exact: "1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -265,15 +265,15 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
query_param = [
|
query_param = [
|
||||||
{
|
{
|
||||||
name = "hack1"
|
name = "hack1"
|
||||||
|
present = true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "hack2"
|
name = "hack2"
|
||||||
value = "1"
|
exact = "1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "hack3"
|
name = "hack3"
|
||||||
value = "a.*z"
|
regex = "a.*z"
|
||||||
regex = true
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -343,15 +343,15 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
QueryParam = [
|
QueryParam = [
|
||||||
{
|
{
|
||||||
Name = "hack1"
|
Name = "hack1"
|
||||||
|
Present = true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = "hack2"
|
Name = "hack2"
|
||||||
Value = "1"
|
Exact = "1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = "hack3"
|
Name = "hack3"
|
||||||
Value = "a.*z"
|
Regex = "a.*z"
|
||||||
Regex = true
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -421,15 +421,15 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
QueryParam: []api.ServiceRouteHTTPMatchQueryParam{
|
QueryParam: []api.ServiceRouteHTTPMatchQueryParam{
|
||||||
{
|
{
|
||||||
Name: "hack1",
|
Name: "hack1",
|
||||||
|
Present: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "hack2",
|
Name: "hack2",
|
||||||
Value: "1",
|
Exact: "1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "hack3",
|
Name: "hack3",
|
||||||
Value: "a.*z",
|
Regex: "a.*z",
|
||||||
Regex: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -84,7 +84,7 @@ routes = [
|
||||||
query_param = [
|
query_param = [
|
||||||
{
|
{
|
||||||
name = "x-debug"
|
name = "x-debug"
|
||||||
value = "1"
|
exact = "1"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -176,16 +176,22 @@ routes = [
|
||||||
- `Name` `(string: <required>)` - The name of the query parameter to
|
- `Name` `(string: <required>)` - The name of the query parameter to
|
||||||
match on.
|
match on.
|
||||||
|
|
||||||
- `Value` `(string: <required>)` - String to match against the query
|
- `Present` `(bool: false)` - Match if the query parameter with the given name
|
||||||
parameter value. The behavior changes with the definition of the
|
is present with any value.
|
||||||
`Regex` field.
|
|
||||||
|
|
||||||
- `Regex` `(bool: false)` - Controls how the `Value` field is used. If
|
At most only one of `Exact`, `Regex`, or `Present` may be configured.
|
||||||
`Regex` is `false` then `Value` matches exactly. If `Regex` is
|
|
||||||
`true` then `Value` matches as a regular expression pattern.
|
|
||||||
|
|
||||||
The syntax when using the Envoy proxy is [documented
|
- `Exact` `(string: "")` - Match if the query parameter with the given
|
||||||
here](https://en.cppreference.com/w/cpp/regex/ecmascript).
|
name is this value.
|
||||||
|
|
||||||
|
At most only one of `Exact`, `Regex`, or `Present` may be configured.
|
||||||
|
|
||||||
|
- `Regex` `(string: "")` - Match if the query parameter with the given
|
||||||
|
name matches this pattern.
|
||||||
|
|
||||||
|
The syntax when using the Envoy proxy is [documented here](https://en.cppreference.com/w/cpp/regex/ecmascript).
|
||||||
|
|
||||||
|
At most only one of `Exact`, `Regex`, or `Present` may be configured.
|
||||||
|
|
||||||
- `Destination` `(ServiceRouteDestination: <optional>)` - Controls how to
|
- `Destination` `(ServiceRouteDestination: <optional>)` - Controls how to
|
||||||
proxy the actual matching request to a service.
|
proxy the actual matching request to a service.
|
||||||
|
|
Loading…
Reference in New Issue