Add agent.segment interpolation to prepared queries
This commit is contained in:
parent
107d7f6c5a
commit
1c04f1537a
|
@ -357,8 +357,7 @@ type NetworkSegment struct {
|
||||||
Name string `mapstructure:"name"`
|
Name string `mapstructure:"name"`
|
||||||
|
|
||||||
// Bind is the bind address for this segment.
|
// Bind is the bind address for this segment.
|
||||||
Bind string `mapstructure:"bind"`
|
Bind string `mapstructure:"bind"`
|
||||||
BindAddrs []string `mapstructure:"-"`
|
|
||||||
|
|
||||||
// Port is the port for this segment.
|
// Port is the port for this segment.
|
||||||
Port int `mapstructure:"port"`
|
Port int `mapstructure:"port"`
|
||||||
|
@ -1467,7 +1466,7 @@ func DecodeConfig(r io.Reader) (*Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate node meta fields
|
// Validate node meta fields
|
||||||
if err := structs.ValidateMetadata(result.Meta); err != nil {
|
if err := structs.ValidateMetadata(result.Meta, false); err != nil {
|
||||||
return nil, fmt.Errorf("Failed to parse node metadata: %v", err)
|
return nil, fmt.Errorf("Failed to parse node metadata: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -597,11 +597,10 @@ func TestDecodeConfig(t *testing.T) {
|
||||||
c: &Config{Segment: "thing"},
|
c: &Config{Segment: "thing"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"server": true, "segments":[{"name": "alpha", "bind": "127.0.0.1", "port": 1234, "rpc_listener": true, "advertise": "1.1.1.1"}]}`,
|
in: `{"segments":[{"name": "alpha", "bind": "127.0.0.1", "port": 1234, "rpc_listener": true, "advertise": "1.1.1.1"}]}`,
|
||||||
c: &Config{Server: true, Segments: []NetworkSegment{{
|
c: &Config{Segments: []NetworkSegment{{
|
||||||
Name: "alpha",
|
Name: "alpha",
|
||||||
Bind: "127.0.0.1",
|
Bind: "127.0.0.1",
|
||||||
BindAddrs: []string{"127.0.0.1"},
|
|
||||||
Port: 1234,
|
Port: 1234,
|
||||||
RPCListener: true,
|
RPCListener: true,
|
||||||
Advertise: "1.1.1.1",
|
Advertise: "1.1.1.1",
|
||||||
|
|
|
@ -89,7 +89,7 @@ func Compile(query *structs.PreparedQuery) (*CompiledTemplate, error) {
|
||||||
// prefix it will be expected to run with. The results might not make
|
// prefix it will be expected to run with. The results might not make
|
||||||
// sense and create a valid service to lookup, but it should render
|
// sense and create a valid service to lookup, but it should render
|
||||||
// without any errors.
|
// without any errors.
|
||||||
if _, err = ct.Render(ct.query.Name); err != nil {
|
if _, err = ct.Render(ct.query.Name, structs.QuerySource{}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ func Compile(query *structs.PreparedQuery) (*CompiledTemplate, error) {
|
||||||
// Render takes a compiled template and renders it for the given name. For
|
// Render takes a compiled template and renders it for the given name. For
|
||||||
// example, if the user looks up foobar.query.consul via DNS then we will call
|
// example, if the user looks up foobar.query.consul via DNS then we will call
|
||||||
// this function with "foobar" on the compiled template.
|
// this function with "foobar" on the compiled template.
|
||||||
func (ct *CompiledTemplate) Render(name string) (*structs.PreparedQuery, error) {
|
func (ct *CompiledTemplate) Render(name string, source structs.QuerySource) (*structs.PreparedQuery, error) {
|
||||||
// Make it "safe" to render a default structure.
|
// Make it "safe" to render a default structure.
|
||||||
if ct == nil {
|
if ct == nil {
|
||||||
return nil, fmt.Errorf("Cannot render an uncompiled template")
|
return nil, fmt.Errorf("Cannot render an uncompiled template")
|
||||||
|
@ -156,6 +156,10 @@ func (ct *CompiledTemplate) Render(name string) (*structs.PreparedQuery, error)
|
||||||
Type: ast.TypeString,
|
Type: ast.TypeString,
|
||||||
Value: strings.TrimPrefix(name, query.Name),
|
Value: strings.TrimPrefix(name, query.Name),
|
||||||
},
|
},
|
||||||
|
"agent.segment": ast.Variable{
|
||||||
|
Type: ast.TypeString,
|
||||||
|
Value: source.Segment,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
FuncMap: map[string]ast.Function{
|
FuncMap: map[string]ast.Function{
|
||||||
"match": match,
|
"match": match,
|
||||||
|
|
|
@ -29,6 +29,7 @@ var (
|
||||||
"${match(0)}",
|
"${match(0)}",
|
||||||
"${match(1)}",
|
"${match(1)}",
|
||||||
"${match(2)}",
|
"${match(2)}",
|
||||||
|
"${agent.segment}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
|
@ -38,11 +39,13 @@ var (
|
||||||
"${match(0)}",
|
"${match(0)}",
|
||||||
"${match(1)}",
|
"${match(1)}",
|
||||||
"${match(2)}",
|
"${match(2)}",
|
||||||
|
"${agent.segment}",
|
||||||
},
|
},
|
||||||
NodeMeta: map[string]string{
|
NodeMeta: map[string]string{
|
||||||
"foo": "${name.prefix}",
|
"foo": "${name.prefix}",
|
||||||
"bar": "${match(0)}",
|
"bar": "${match(0)}",
|
||||||
"baz": "${match(1)}",
|
"baz": "${match(1)}",
|
||||||
|
"zoo": "${agent.segment}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -83,7 +86,7 @@ func renderBench(b *testing.B, query *structs.PreparedQuery) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := compiled.Render("hello-bench-mark")
|
_, err := compiled.Render("hello-bench-mark", structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("err: %v", err)
|
b.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -121,7 +124,7 @@ func TestTemplate_Compile(t *testing.T) {
|
||||||
query.Template.Type = structs.QueryTemplateTypeNamePrefixMatch
|
query.Template.Type = structs.QueryTemplateTypeNamePrefixMatch
|
||||||
query.Template.Regexp = "^(hello)there$"
|
query.Template.Regexp = "^(hello)there$"
|
||||||
query.Service.Service = "${name.full}"
|
query.Service.Service = "${name.full}"
|
||||||
query.Service.Tags = []string{"${match(1)}"}
|
query.Service.Tags = []string{"${match(1)}", "${agent.segment}"}
|
||||||
backup, err := copystructure.Copy(query)
|
backup, err := copystructure.Copy(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
|
@ -135,7 +138,7 @@ func TestTemplate_Compile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do a sanity check render on it.
|
// Do a sanity check render on it.
|
||||||
actual, err := ct.Render("hellothere")
|
actual, err := ct.Render("hellothere", structs.QuerySource{Segment: "segment-foo"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -150,6 +153,7 @@ func TestTemplate_Compile(t *testing.T) {
|
||||||
Service: "hellothere",
|
Service: "hellothere",
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
"hello",
|
"hello",
|
||||||
|
"segment-foo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -201,7 +205,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual, err := ct.Render("unused")
|
actual, err := ct.Render("unused", structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -218,7 +222,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
Regexp: "^(.*?)-(.*?)-(.*)$",
|
Regexp: "^(.*?)-(.*?)-(.*)$",
|
||||||
},
|
},
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "${name.prefix} xxx ${name.full} xxx ${name.suffix}",
|
Service: "${name.prefix} xxx ${name.full} xxx ${name.suffix} xxx ${agent.segment}",
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
"${match(-1)}",
|
"${match(-1)}",
|
||||||
"${match(0)}",
|
"${match(0)}",
|
||||||
|
@ -238,7 +242,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
|
|
||||||
// Run a case that matches the regexp.
|
// Run a case that matches the regexp.
|
||||||
{
|
{
|
||||||
actual, err := ct.Render("hello-foo-bar-none")
|
actual, err := ct.Render("hello-foo-bar-none", structs.QuerySource{Segment: "segment-bar"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -249,7 +253,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
Regexp: "^(.*?)-(.*?)-(.*)$",
|
Regexp: "^(.*?)-(.*?)-(.*)$",
|
||||||
},
|
},
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "hello- xxx hello-foo-bar-none xxx foo-bar-none",
|
Service: "hello- xxx hello-foo-bar-none xxx foo-bar-none xxx segment-bar",
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
"",
|
"",
|
||||||
"hello-foo-bar-none",
|
"hello-foo-bar-none",
|
||||||
|
@ -269,7 +273,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
|
|
||||||
// Run a case that doesn't match the regexp
|
// Run a case that doesn't match the regexp
|
||||||
{
|
{
|
||||||
actual, err := ct.Render("hello-nope")
|
actual, err := ct.Render("hello-nope", structs.QuerySource{Segment: "segment-bar"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -280,7 +284,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
Regexp: "^(.*?)-(.*?)-(.*)$",
|
Regexp: "^(.*?)-(.*?)-(.*)$",
|
||||||
},
|
},
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "hello- xxx hello-nope xxx nope",
|
Service: "hello- xxx hello-nope xxx nope xxx segment-bar",
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
@ -307,7 +311,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
RemoveEmptyTags: true,
|
RemoveEmptyTags: true,
|
||||||
},
|
},
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "${name.prefix} xxx ${name.full} xxx ${name.suffix}",
|
Service: "${name.prefix} xxx ${name.full} xxx ${name.suffix} xxx ${agent.segment}",
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
"${match(-1)}",
|
"${match(-1)}",
|
||||||
"${match(0)}",
|
"${match(0)}",
|
||||||
|
@ -326,7 +330,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
|
|
||||||
// Run a case that matches the regexp, removing empty tags.
|
// Run a case that matches the regexp, removing empty tags.
|
||||||
{
|
{
|
||||||
actual, err := ct.Render("hello-foo-bar-none")
|
actual, err := ct.Render("hello-foo-bar-none", structs.QuerySource{Segment: "segment-baz"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -338,7 +342,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
RemoveEmptyTags: true,
|
RemoveEmptyTags: true,
|
||||||
},
|
},
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "hello- xxx hello-foo-bar-none xxx foo-bar-none",
|
Service: "hello- xxx hello-foo-bar-none xxx foo-bar-none xxx segment-baz",
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
"hello-foo-bar-none",
|
"hello-foo-bar-none",
|
||||||
"hello",
|
"hello",
|
||||||
|
@ -355,7 +359,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
|
|
||||||
// Run a case that doesn't match the regexp, removing empty tags.
|
// Run a case that doesn't match the regexp, removing empty tags.
|
||||||
{
|
{
|
||||||
actual, err := ct.Render("hello-nope")
|
actual, err := ct.Render("hello-nope", structs.QuerySource{Segment: "segment-baz"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -367,7 +371,7 @@ func TestTemplate_Render(t *testing.T) {
|
||||||
RemoveEmptyTags: true,
|
RemoveEmptyTags: true,
|
||||||
},
|
},
|
||||||
Service: structs.ServiceQuery{
|
Service: structs.ServiceQuery{
|
||||||
Service: "hello- xxx hello-nope xxx nope",
|
Service: "hello- xxx hello-nope xxx nope xxx segment-baz",
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
"42",
|
"42",
|
||||||
},
|
},
|
||||||
|
|
|
@ -182,7 +182,7 @@ func parseService(svc *structs.ServiceQuery) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the metadata filters are valid
|
// Make sure the metadata filters are valid
|
||||||
if err := structs.ValidateMetadata(svc.NodeMeta); err != nil {
|
if err := structs.ValidateMetadata(svc.NodeMeta, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,7 +298,7 @@ func (p *PreparedQuery) Explain(args *structs.PreparedQueryExecuteRequest,
|
||||||
|
|
||||||
// Try to locate the query.
|
// Try to locate the query.
|
||||||
state := p.srv.fsm.State()
|
state := p.srv.fsm.State()
|
||||||
_, query, err := state.PreparedQueryResolve(args.QueryIDOrName)
|
_, query, err := state.PreparedQueryResolve(args.QueryIDOrName, args.Agent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -345,7 +345,7 @@ func (p *PreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest,
|
||||||
|
|
||||||
// Try to locate the query.
|
// Try to locate the query.
|
||||||
state := p.srv.fsm.State()
|
state := p.srv.fsm.State()
|
||||||
_, query, err := state.PreparedQueryResolve(args.QueryIDOrName)
|
_, query, err := state.PreparedQueryResolve(args.QueryIDOrName, args.Agent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,7 +256,7 @@ func (s *Store) PreparedQueryGet(ws memdb.WatchSet, queryID string) (uint64, *st
|
||||||
// PreparedQueryResolve returns the given prepared query by looking up an ID or
|
// PreparedQueryResolve returns the given prepared query by looking up an ID or
|
||||||
// Name. If the query was looked up by name and it's a template, then the
|
// Name. If the query was looked up by name and it's a template, then the
|
||||||
// template will be rendered before it is returned.
|
// template will be rendered before it is returned.
|
||||||
func (s *Store) PreparedQueryResolve(queryIDOrName string) (uint64, *structs.PreparedQuery, error) {
|
func (s *Store) PreparedQueryResolve(queryIDOrName string, source structs.QuerySource) (uint64, *structs.PreparedQuery, error) {
|
||||||
tx := s.db.Txn(false)
|
tx := s.db.Txn(false)
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
|
|
||||||
|
@ -293,7 +293,7 @@ func (s *Store) PreparedQueryResolve(queryIDOrName string) (uint64, *structs.Pre
|
||||||
prep := func(wrapped interface{}) (uint64, *structs.PreparedQuery, error) {
|
prep := func(wrapped interface{}) (uint64, *structs.PreparedQuery, error) {
|
||||||
wrapper := wrapped.(*queryWrapper)
|
wrapper := wrapped.(*queryWrapper)
|
||||||
if prepared_query.IsTemplate(wrapper.PreparedQuery) {
|
if prepared_query.IsTemplate(wrapper.PreparedQuery) {
|
||||||
render, err := wrapper.ct.Render(queryIDOrName)
|
render, err := wrapper.ct.Render(queryIDOrName, source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return idx, nil, err
|
return idx, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -554,7 +554,7 @@ func TestStateStore_PreparedQueryResolve(t *testing.T) {
|
||||||
|
|
||||||
// Try to lookup a query that's not there using something that looks
|
// Try to lookup a query that's not there using something that looks
|
||||||
// like a real ID.
|
// like a real ID.
|
||||||
idx, actual, err := s.PreparedQueryResolve(query.ID)
|
idx, actual, err := s.PreparedQueryResolve(query.ID, structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -567,7 +567,7 @@ func TestStateStore_PreparedQueryResolve(t *testing.T) {
|
||||||
|
|
||||||
// Try to lookup a query that's not there using something that looks
|
// Try to lookup a query that's not there using something that looks
|
||||||
// like a name
|
// like a name
|
||||||
idx, actual, err = s.PreparedQueryResolve(query.Name)
|
idx, actual, err = s.PreparedQueryResolve(query.Name, structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -600,7 +600,7 @@ func TestStateStore_PreparedQueryResolve(t *testing.T) {
|
||||||
ModifyIndex: 3,
|
ModifyIndex: 3,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
idx, actual, err = s.PreparedQueryResolve(query.ID)
|
idx, actual, err = s.PreparedQueryResolve(query.ID, structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -612,7 +612,7 @@ func TestStateStore_PreparedQueryResolve(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read it back using the name and verify it again.
|
// Read it back using the name and verify it again.
|
||||||
idx, actual, err = s.PreparedQueryResolve(query.Name)
|
idx, actual, err = s.PreparedQueryResolve(query.Name, structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -625,7 +625,7 @@ func TestStateStore_PreparedQueryResolve(t *testing.T) {
|
||||||
|
|
||||||
// Make sure an empty lookup is well-behaved if there are actual queries
|
// Make sure an empty lookup is well-behaved if there are actual queries
|
||||||
// in the state store.
|
// in the state store.
|
||||||
idx, actual, err = s.PreparedQueryResolve("")
|
idx, actual, err = s.PreparedQueryResolve("", structs.QuerySource{})
|
||||||
if err != ErrMissingQueryID {
|
if err != ErrMissingQueryID {
|
||||||
t.Fatalf("bad: %v ", err)
|
t.Fatalf("bad: %v ", err)
|
||||||
}
|
}
|
||||||
|
@ -681,7 +681,7 @@ func TestStateStore_PreparedQueryResolve(t *testing.T) {
|
||||||
ModifyIndex: 4,
|
ModifyIndex: 4,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
idx, actual, err = s.PreparedQueryResolve("prod-mongodb")
|
idx, actual, err = s.PreparedQueryResolve("prod-mongodb", structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -708,7 +708,7 @@ func TestStateStore_PreparedQueryResolve(t *testing.T) {
|
||||||
ModifyIndex: 5,
|
ModifyIndex: 5,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
idx, actual, err = s.PreparedQueryResolve("prod-redis-foobar")
|
idx, actual, err = s.PreparedQueryResolve("prod-redis-foobar", structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -735,7 +735,7 @@ func TestStateStore_PreparedQueryResolve(t *testing.T) {
|
||||||
ModifyIndex: 4,
|
ModifyIndex: 4,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
idx, actual, err = s.PreparedQueryResolve("prod-")
|
idx, actual, err = s.PreparedQueryResolve("prod-", structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -748,7 +748,7 @@ func TestStateStore_PreparedQueryResolve(t *testing.T) {
|
||||||
|
|
||||||
// Make sure you can't run a prepared query template by ID, since that
|
// Make sure you can't run a prepared query template by ID, since that
|
||||||
// makes no sense.
|
// makes no sense.
|
||||||
_, _, err = s.PreparedQueryResolve(tmpl1.ID)
|
_, _, err = s.PreparedQueryResolve(tmpl1.ID, structs.QuerySource{})
|
||||||
if err == nil || !strings.Contains(err.Error(), "prepared query templates can only be resolved up by name") {
|
if err == nil || !strings.Contains(err.Error(), "prepared query templates can only be resolved up by name") {
|
||||||
t.Fatalf("bad: %v", err)
|
t.Fatalf("bad: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -960,7 +960,7 @@ func TestStateStore_PreparedQuery_Snapshot_Restore(t *testing.T) {
|
||||||
|
|
||||||
// Make sure the second query, which is a template, was compiled
|
// Make sure the second query, which is a template, was compiled
|
||||||
// and can be resolved.
|
// and can be resolved.
|
||||||
_, query, err := s.PreparedQueryResolve("bob-backwards-is-bob")
|
_, query, err := s.PreparedQueryResolve("bob-backwards-is-bob", structs.QuerySource{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ func (s *HTTPServer) preparedQueryExecute(id string, resp http.ResponseWriter, r
|
||||||
Agent: structs.QuerySource{
|
Agent: structs.QuerySource{
|
||||||
Node: s.agent.config.NodeName,
|
Node: s.agent.config.NodeName,
|
||||||
Datacenter: s.agent.config.Datacenter,
|
Datacenter: s.agent.config.Datacenter,
|
||||||
|
Segment: s.agent.config.Segment,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s.parseSource(req, &args.Source)
|
s.parseSource(req, &args.Source)
|
||||||
|
@ -140,6 +141,7 @@ func (s *HTTPServer) preparedQueryExplain(id string, resp http.ResponseWriter, r
|
||||||
Agent: structs.QuerySource{
|
Agent: structs.QuerySource{
|
||||||
Node: s.agent.config.NodeName,
|
Node: s.agent.config.NodeName,
|
||||||
Datacenter: s.agent.config.Datacenter,
|
Datacenter: s.agent.config.Datacenter,
|
||||||
|
Segment: s.agent.config.Segment,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s.parseSource(req, &args.Source)
|
s.parseSource(req, &args.Source)
|
||||||
|
|
|
@ -242,6 +242,7 @@ func (r *DeregisterRequest) RequestDatacenter() string {
|
||||||
// coordinates.
|
// coordinates.
|
||||||
type QuerySource struct {
|
type QuerySource struct {
|
||||||
Datacenter string
|
Datacenter string
|
||||||
|
Segment string
|
||||||
Node string
|
Node string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,13 +311,13 @@ type Node struct {
|
||||||
type Nodes []*Node
|
type Nodes []*Node
|
||||||
|
|
||||||
// ValidateMeta validates a set of key/value pairs from the agent config
|
// ValidateMeta validates a set of key/value pairs from the agent config
|
||||||
func ValidateMetadata(meta map[string]string) error {
|
func ValidateMetadata(meta map[string]string, allowConsulPrefix bool) error {
|
||||||
if len(meta) > metaMaxKeyPairs {
|
if len(meta) > metaMaxKeyPairs {
|
||||||
return fmt.Errorf("Node metadata cannot contain more than %d key/value pairs", metaMaxKeyPairs)
|
return fmt.Errorf("Node metadata cannot contain more than %d key/value pairs", metaMaxKeyPairs)
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range meta {
|
for key, value := range meta {
|
||||||
if err := validateMetaPair(key, value); err != nil {
|
if err := validateMetaPair(key, value, allowConsulPrefix); err != nil {
|
||||||
return fmt.Errorf("Couldn't load metadata pair ('%s', '%s'): %s", key, value, err)
|
return fmt.Errorf("Couldn't load metadata pair ('%s', '%s'): %s", key, value, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,7 +326,7 @@ func ValidateMetadata(meta map[string]string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateMetaPair checks that the given key/value pair is in a valid format
|
// validateMetaPair checks that the given key/value pair is in a valid format
|
||||||
func validateMetaPair(key, value string) error {
|
func validateMetaPair(key, value string, allowConsulPrefix bool) error {
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return fmt.Errorf("Key cannot be blank")
|
return fmt.Errorf("Key cannot be blank")
|
||||||
}
|
}
|
||||||
|
@ -335,7 +336,7 @@ func validateMetaPair(key, value string) error {
|
||||||
if len(key) > metaKeyMaxLength {
|
if len(key) > metaKeyMaxLength {
|
||||||
return fmt.Errorf("Key is too long (limit: %d characters)", metaKeyMaxLength)
|
return fmt.Errorf("Key is too long (limit: %d characters)", metaKeyMaxLength)
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(key, metaKeyReservedPrefix) {
|
if strings.HasPrefix(key, metaKeyReservedPrefix) && !allowConsulPrefix {
|
||||||
return fmt.Errorf("Key prefix '%s' is reserved for internal use", metaKeyReservedPrefix)
|
return fmt.Errorf("Key prefix '%s' is reserved for internal use", metaKeyReservedPrefix)
|
||||||
}
|
}
|
||||||
if len(value) > metaValueMaxLength {
|
if len(value) > metaValueMaxLength {
|
||||||
|
|
|
@ -482,7 +482,7 @@ func TestStructs_ValidateMetadata(t *testing.T) {
|
||||||
"key2": "value2",
|
"key2": "value2",
|
||||||
}
|
}
|
||||||
// Should succeed
|
// Should succeed
|
||||||
if err := ValidateMetadata(meta); err != nil {
|
if err := ValidateMetadata(meta, false); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,7 +490,7 @@ func TestStructs_ValidateMetadata(t *testing.T) {
|
||||||
meta = map[string]string{
|
meta = map[string]string{
|
||||||
"": "value1",
|
"": "value1",
|
||||||
}
|
}
|
||||||
if err := ValidateMetadata(meta); !strings.Contains(err.Error(), "Couldn't load metadata pair") {
|
if err := ValidateMetadata(meta, false); !strings.Contains(err.Error(), "Couldn't load metadata pair") {
|
||||||
t.Fatalf("should have failed")
|
t.Fatalf("should have failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,7 +499,7 @@ func TestStructs_ValidateMetadata(t *testing.T) {
|
||||||
for i := 0; i < metaMaxKeyPairs+1; i++ {
|
for i := 0; i < metaMaxKeyPairs+1; i++ {
|
||||||
meta[string(i)] = "value"
|
meta[string(i)] = "value"
|
||||||
}
|
}
|
||||||
if err := ValidateMetadata(meta); !strings.Contains(err.Error(), "cannot contain more than") {
|
if err := ValidateMetadata(meta, false); !strings.Contains(err.Error(), "cannot contain more than") {
|
||||||
t.Fatalf("should have failed")
|
t.Fatalf("should have failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -529,7 +529,7 @@ func TestStructs_validateMetaPair(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pair := range pairs {
|
for _, pair := range pairs {
|
||||||
err := validateMetaPair(pair.Key, pair.Value)
|
err := validateMetaPair(pair.Key, pair.Value, false)
|
||||||
if pair.Error == "" && err != nil {
|
if pair.Error == "" && err != nil {
|
||||||
t.Fatalf("should have succeeded: %v, %v", pair, err)
|
t.Fatalf("should have succeeded: %v, %v", pair, err)
|
||||||
} else if pair.Error != "" && !strings.Contains(err.Error(), pair.Error) {
|
} else if pair.Error != "" && !strings.Contains(err.Error(), pair.Error) {
|
||||||
|
|
|
@ -225,7 +225,7 @@ func (cmd *AgentCommand) readConfig() *agent.Config {
|
||||||
key, value := agent.ParseMetaPair(entry)
|
key, value := agent.ParseMetaPair(entry)
|
||||||
cmdCfg.Meta[key] = value
|
cmdCfg.Meta[key] = value
|
||||||
}
|
}
|
||||||
if err := structs.ValidateMetadata(cmdCfg.Meta); err != nil {
|
if err := structs.ValidateMetadata(cmdCfg.Meta, false); err != nil {
|
||||||
cmd.UI.Error(fmt.Sprintf("Failed to parse node metadata: %v", err))
|
cmd.UI.Error(fmt.Sprintf("Failed to parse node metadata: %v", err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,25 @@ populate the query before it is executed. All of the string fields inside the
|
||||||
doesn't match, or an invalid index is given, then `${match(N)}` will return an
|
doesn't match, or an invalid index is given, then `${match(N)}` will return an
|
||||||
empty string.
|
empty string.
|
||||||
|
|
||||||
|
- `${agent.segment}` has the network segment (Enterprise only) of the agent that
|
||||||
|
initiated the query. This can be used with the `NodeMeta` field to limit the results
|
||||||
|
of a query to service instances within its own network segment:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Name": "",
|
||||||
|
"Template": {
|
||||||
|
"Type": "name_prefix_match"
|
||||||
|
},
|
||||||
|
"Service": {
|
||||||
|
"Service": "${name.full}",
|
||||||
|
"NodeMeta": {"consul-network-segment": "${agent.segment}"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This will map all names of the form "<service>.query.consul" over DNS to a query
|
||||||
|
that will select an instance of the service in the agent's own network segment.
|
||||||
|
|
||||||
Using templates, it is possible to apply prepared query behaviors to many
|
Using templates, it is possible to apply prepared query behaviors to many
|
||||||
services with a single template. Here's an example template that matches any
|
services with a single template. Here's an example template that matches any
|
||||||
query and applies a failover policy to it:
|
query and applies a failover policy to it:
|
||||||
|
|
Loading…
Reference in New Issue