2016-02-26 08:25:19 +00:00
|
|
|
package prepared_query
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
pkg refactor
command/agent/* -> agent/*
command/consul/* -> agent/consul/*
command/agent/command{,_test}.go -> command/agent{,_test}.go
command/base/command.go -> command/base.go
command/base/* -> command/*
commands.go -> command/commands.go
The script which did the refactor is:
(
cd $GOPATH/src/github.com/hashicorp/consul
git mv command/agent/command.go command/agent.go
git mv command/agent/command_test.go command/agent_test.go
git mv command/agent/flag_slice_value{,_test}.go command/
git mv command/agent .
git mv command/base/command.go command/base.go
git mv command/base/config_util{,_test}.go command/
git mv commands.go command/
git mv consul agent
rmdir command/base/
gsed -i -e 's|package agent|package command|' command/agent{,_test}.go
gsed -i -e 's|package agent|package command|' command/flag_slice_value{,_test}.go
gsed -i -e 's|package base|package command|' command/base.go command/config_util{,_test}.go
gsed -i -e 's|package main|package command|' command/commands.go
gsed -i -e 's|base.Command|BaseCommand|' command/commands.go
gsed -i -e 's|agent.Command|AgentCommand|' command/commands.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/commands.go
gsed -i -e 's|base\.||' command/commands.go
gsed -i -e 's|command\.||' command/commands.go
gsed -i -e 's|command|c|' main.go
gsed -i -e 's|range Commands|range command.Commands|' main.go
gsed -i -e 's|Commands: Commands|Commands: command.Commands|' main.go
gsed -i -e 's|base\.BoolValue|BoolValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.DurationValue|DurationValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.StringValue|StringValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.UintValue|UintValue|' command/operator_autopilot_set.go
gsed -i -e 's|\bCommand\b|BaseCommand|' command/base.go
gsed -i -e 's|BaseCommand Options|Command Options|' command/base.go
gsed -i -e 's|base.Command|BaseCommand|' command/*.go
gsed -i -e 's|c\.Command|c.BaseCommand|g' command/*.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/*_test.go
gsed -i -e 's|base\.||' command/*_test.go
gsed -i -e 's|\bCommand\b|AgentCommand|' command/agent{,_test}.go
gsed -i -e 's|cmd.AgentCommand|cmd.BaseCommand|' command/agent.go
gsed -i -e 's|cli.AgentCommand = new(Command)|cli.Command = new(AgentCommand)|' command/agent_test.go
gsed -i -e 's|exec.AgentCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|exec.BaseCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|NewTestAgent|agent.NewTestAgent|' command/agent_test.go
gsed -i -e 's|= TestConfig|= agent.TestConfig|' command/agent_test.go
gsed -i -e 's|: RetryJoin|: agent.RetryJoin|' command/agent_test.go
gsed -i -e 's|\.\./\.\./|../|' command/config_util_test.go
gsed -i -e 's|\bverifyUniqueListeners|VerifyUniqueListeners|' agent/config{,_test}.go command/agent.go
gsed -i -e 's|\bserfLANKeyring\b|SerfLANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bserfWANKeyring\b|SerfWANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bNewAgent\b|agent.New|g' command/agent{,_test}.go
gsed -i -e 's|\bNewAgent|New|' agent/{acl_test,agent,testagent}.go
gsed -i -e 's|\bAgent\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bBool\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDefaultConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDevConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bMergeConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bReadConfigPaths\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bParseMetaPair\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfLANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfWANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|circonus\.agent|circonus|g' command/agent{,_test}.go
gsed -i -e 's|logger\.agent|logger|g' command/agent{,_test}.go
gsed -i -e 's|metrics\.agent|metrics|g' command/agent{,_test}.go
gsed -i -e 's|// agent.Agent|// agent|' command/agent{,_test}.go
gsed -i -e 's|a\.agent\.Config|a.Config|' command/agent{,_test}.go
gsed -i -e 's|agent\.AppendSliceValue|AppendSliceValue|' command/{configtest,validate}.go
gsed -i -e 's|consul/consul|agent/consul|' GNUmakefile
gsed -i -e 's|\.\./test|../../test|' agent/consul/server_test.go
# fix imports
f=$(grep -rl 'github.com/hashicorp/consul/command/agent' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/command/agent|github.com/hashicorp/consul/agent|' $f
goimports -w $f
f=$(grep -rl 'github.com/hashicorp/consul/consul' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/consul|github.com/hashicorp/consul/agent/consul|' $f
goimports -w $f
goimports -w command/*.go main.go
)
2017-06-09 22:28:28 +00:00
|
|
|
"github.com/hashicorp/consul/agent/consul/structs"
|
2016-02-26 08:25:19 +00:00
|
|
|
"github.com/hashicorp/hil"
|
|
|
|
"github.com/hashicorp/hil/ast"
|
|
|
|
"github.com/mitchellh/copystructure"
|
|
|
|
)
|
|
|
|
|
|
|
|
// IsTemplate returns true if the given query is a template.
|
|
|
|
func IsTemplate(query *structs.PreparedQuery) bool {
|
|
|
|
return query.Template.Type != ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// CompiledTemplate is an opaque object that can be used later to render a
|
|
|
|
// prepared query template.
|
|
|
|
type CompiledTemplate struct {
|
|
|
|
// query keeps a copy of the original query for rendering.
|
|
|
|
query *structs.PreparedQuery
|
|
|
|
|
|
|
|
// trees contains a map with paths to string fields in a structure to
|
|
|
|
// parsed syntax trees, suitable for later evaluation.
|
|
|
|
trees map[string]ast.Node
|
|
|
|
|
|
|
|
// re is the compiled regexp, if they supplied one (this can be nil).
|
|
|
|
re *regexp.Regexp
|
|
|
|
}
|
|
|
|
|
2016-02-26 10:00:20 +00:00
|
|
|
// Compile validates a prepared query template and returns an opaque compiled
|
|
|
|
// object that can be used later to render the template.
|
|
|
|
func Compile(query *structs.PreparedQuery) (*CompiledTemplate, error) {
|
|
|
|
// Make sure it's a type we understand.
|
|
|
|
if query.Template.Type != structs.QueryTemplateTypeNamePrefixMatch {
|
|
|
|
return nil, fmt.Errorf("Bad Template.Type '%s'", query.Template.Type)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start compile.
|
|
|
|
ct := &CompiledTemplate{
|
|
|
|
trees: make(map[string]ast.Node),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a copy of the query to use as the basis for rendering later.
|
|
|
|
dup, err := copystructure.Copy(query)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var ok bool
|
|
|
|
ct.query, ok = dup.(*structs.PreparedQuery)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Failed to copy query")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Walk over all the string fields in the Service sub-structure and
|
|
|
|
// parse them as HIL.
|
|
|
|
parse := func(path string, v reflect.Value) error {
|
|
|
|
tree, err := hil.Parse(v.String())
|
|
|
|
if err != nil {
|
2016-02-27 04:03:15 +00:00
|
|
|
return fmt.Errorf("Bad format '%s' in Service%s: %s", v.String(), path, err)
|
2016-02-26 10:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ct.trees[path] = tree
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err := walk(&ct.query.Service, parse); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If they supplied a regexp then compile it.
|
|
|
|
if ct.query.Template.Regexp != "" {
|
|
|
|
var err error
|
|
|
|
ct.re, err = regexp.Compile(ct.query.Template.Regexp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Bad Regexp: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-27 04:03:15 +00:00
|
|
|
// Finally do a test render with the supplied name prefix. This will
|
|
|
|
// help catch errors before run time, and this is the most minimal
|
|
|
|
// 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
|
|
|
|
// without any errors.
|
|
|
|
if _, err = ct.Render(ct.query.Name); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-02-26 10:00:20 +00:00
|
|
|
return ct, nil
|
|
|
|
}
|
|
|
|
|
2016-02-26 08:25:19 +00:00
|
|
|
// 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
|
|
|
|
// this function with "foobar" on the compiled template.
|
|
|
|
func (ct *CompiledTemplate) Render(name string) (*structs.PreparedQuery, error) {
|
2016-02-26 20:07:43 +00:00
|
|
|
// Make it "safe" to render a default structure.
|
|
|
|
if ct == nil {
|
|
|
|
return nil, fmt.Errorf("Cannot render an uncompiled template")
|
|
|
|
}
|
|
|
|
|
2016-02-26 08:25:19 +00:00
|
|
|
// Start with a fresh, detached copy of the original so we don't disturb
|
|
|
|
// the prototype.
|
|
|
|
dup, err := copystructure.Copy(ct.query)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
query, ok := dup.(*structs.PreparedQuery)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Failed to copy query")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run the regular expression, if provided. We execute on a copy here
|
|
|
|
// to avoid internal lock contention because we expect this to be called
|
|
|
|
// from multiple goroutines.
|
|
|
|
var matches []string
|
|
|
|
if ct.re != nil {
|
2016-02-27 04:03:15 +00:00
|
|
|
re := ct.re.Copy()
|
|
|
|
matches = re.FindStringSubmatch(name)
|
2016-02-26 08:25:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create a safe match function that can't fail at run time. It will
|
|
|
|
// return an empty string for any invalid input.
|
|
|
|
match := ast.Function{
|
|
|
|
ArgTypes: []ast.Type{ast.TypeInt},
|
|
|
|
ReturnType: ast.TypeString,
|
|
|
|
Variadic: false,
|
|
|
|
Callback: func(inputs []interface{}) (interface{}, error) {
|
|
|
|
i, ok := inputs[0].(int)
|
2016-03-07 21:32:32 +00:00
|
|
|
if ok && i >= 0 && i < len(matches) {
|
2016-02-26 08:25:19 +00:00
|
|
|
return matches[i], nil
|
|
|
|
}
|
2017-04-21 01:59:42 +00:00
|
|
|
return "", nil
|
2016-02-26 08:25:19 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build up the HIL evaluation context.
|
|
|
|
config := &hil.EvalConfig{
|
|
|
|
GlobalScope: &ast.BasicScope{
|
|
|
|
VarMap: map[string]ast.Variable{
|
|
|
|
"name.full": ast.Variable{
|
|
|
|
Type: ast.TypeString,
|
|
|
|
Value: name,
|
|
|
|
},
|
|
|
|
"name.prefix": ast.Variable{
|
|
|
|
Type: ast.TypeString,
|
|
|
|
Value: query.Name,
|
|
|
|
},
|
|
|
|
"name.suffix": ast.Variable{
|
|
|
|
Type: ast.TypeString,
|
|
|
|
Value: strings.TrimPrefix(name, query.Name),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
FuncMap: map[string]ast.Function{
|
|
|
|
"match": match,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run through the Service sub-structure and evaluate all the strings
|
|
|
|
// as HIL.
|
|
|
|
eval := func(path string, v reflect.Value) error {
|
|
|
|
tree, ok := ct.trees[path]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-09 23:53:09 +00:00
|
|
|
res, err := hil.Eval(tree, config)
|
2016-02-26 08:25:19 +00:00
|
|
|
if err != nil {
|
2016-02-27 04:03:15 +00:00
|
|
|
return fmt.Errorf("Bad evaluation for '%s' in Service%s: %s", v.String(), path, err)
|
2016-02-26 08:25:19 +00:00
|
|
|
}
|
2016-08-09 23:53:09 +00:00
|
|
|
if res.Type != hil.TypeString {
|
|
|
|
return fmt.Errorf("Expected Service%s field to be a string, got %s", path, res.Type)
|
2016-02-26 08:25:19 +00:00
|
|
|
}
|
|
|
|
|
2016-08-09 23:53:09 +00:00
|
|
|
v.SetString(res.Value.(string))
|
2016-02-26 08:25:19 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err := walk(&query.Service, eval); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return query, nil
|
|
|
|
}
|