Update github.com/hashicorp/go-discover to pull in new config parser

This patch updates the go-discover library to use the new config parser
which uses a different encoding scheme for the go-discover config DSL.
Values are no longer URL encoded but taken literally unless they contain
spaces, backslashes or double quotes. To support keys or values with
these special characters the string needs to be double quoted and usual
escaping rules apply.

Fixes #3417
This commit is contained in:
Frank Schroeder 2017-10-04 10:20:33 +02:00 committed by Frank Schröder
parent 64adc44583
commit 3d4fa7f65d
3 changed files with 200 additions and 31 deletions

View File

@ -5,16 +5,20 @@
ip addresses of nodes in cloud environments based on meta information ip addresses of nodes in cloud environments based on meta information
like tags provided by the environment. like tags provided by the environment.
The configuration for the providers is provided as a list of `key=val The configuration for the providers is provided as a list of `key=val key=val
key=val ...` tuples where the values can be URL encoded. The provider is ...` tuples. If either the key or the value contains a space (` `), a backslash
determined through the `provider` key. Effectively, only spaces have to (`\`) or double quotes (`"`) then it needs to be quoted with double quotes.
be encoded with a `+` and on the command line you have to observe Within a quoted string you can use the backslash to escape double quotes or the
quoting rules with your shell. backslash itself, e.g. `key=val "some key"="some value"`
Duplicate keys are reported as error and the provider is determined through the
`provider` key.
### Supported Providers ### Supported Providers
The following cloud providers have implementations in the go-discover/provider The following cloud providers have implementations in the go-discover/provider
sub packages. Additional providers can be added through the [Register](https://godoc.org/github.com/hashicorp/go-discover#Register) sub packages. Additional providers can be added through the
[Register](https://godoc.org/github.com/hashicorp/go-discover#Register)
function. function.
* Amazon AWS [Config options](http://godoc.org/github.com/hashicorp/go-discover/provider/aws) * Amazon AWS [Config options](http://godoc.org/github.com/hashicorp/go-discover/provider/aws)

View File

@ -2,8 +2,8 @@ package discover
import ( import (
"fmt" "fmt"
"net/url"
"sort" "sort"
"strconv"
"strings" "strings"
) )
@ -11,28 +11,12 @@ import (
// functions to use. // functions to use.
type Config map[string]string type Config map[string]string
// Parse parses a "key=val key=val ..." string into // Parse parses a "key=val key=val ..." string into a config map. Keys
// a config map. Values are URL escaped. // and values which contain spaces, backslashes or double-quotes must be
// quoted with double quotes. Use the backslash to escape special
// characters within quoted strings, e.g. "some key"="some \"value\"".
func Parse(s string) (Config, error) { func Parse(s string) (Config, error) {
s = strings.TrimSpace(s) return parse(s)
if s == "" {
return nil, nil
}
c := Config{}
for _, v := range strings.Fields(s) {
p := strings.SplitN(v, "=", 2)
if len(p) != 2 {
return nil, fmt.Errorf("invalid format: %s", v)
}
key := p[0]
val, err := url.QueryUnescape(p[1])
if err != nil {
return nil, fmt.Errorf("invalid format: %s", v)
}
c[key] = val
}
return c, nil
} }
// String formats a config map into the "key=val key=val ..." // String formats a config map into the "key=val key=val ..."
@ -48,14 +32,195 @@ func (c Config) String() string {
sort.Strings(keys) sort.Strings(keys)
keys = append([]string{"provider"}, keys...) keys = append([]string{"provider"}, keys...)
quote := func(s string) string {
if strings.ContainsAny(s, ` "\`) {
return strconv.Quote(s)
}
return s
}
var vals []string var vals []string
for _, k := range keys { for _, k := range keys {
v := c[k] v := c[k]
if v == "" { if v == "" {
continue continue
} }
v = k + "=" + url.QueryEscape(v) vals = append(vals, quote(k)+"="+quote(v))
vals = append(vals, v)
} }
return strings.Join(vals, " ") return strings.Join(vals, " ")
} }
func parse(in string) (Config, error) {
m := Config{}
s := []rune(strings.TrimSpace(in))
state := stateKey
key := ""
for {
// exit condition
if len(s) == 0 {
break
}
// get the next token
item, val, n := lex(s)
s = s[n:]
// fmt.Printf("parse: state: %q item: %q val: '%s' n: %d rest: '%s'\n", state, item, val, n, string(s))
switch state {
case stateKey:
switch item {
case itemText:
key = val
if _, exists := m[key]; exists {
return nil, fmt.Errorf("%s: duplicate key", key)
}
state = stateEqual
default:
return nil, fmt.Errorf("%s: %s", key, val)
}
case stateEqual:
switch item {
case itemEqual:
state = stateVal
default:
return nil, fmt.Errorf("%s: missing '='", key)
}
case stateVal:
switch item {
case itemText:
m[key] = val
state = stateKey
case itemError:
return nil, fmt.Errorf("%s: %s", key, val)
default:
return nil, fmt.Errorf("%s: missing value", key)
}
}
}
//fmt.Printf("parse: state: %q rest: '%s'\n", state, string(s))
switch state {
case stateEqual:
return nil, fmt.Errorf("%s: missing '='", key)
case stateVal:
return nil, fmt.Errorf("%s: missing value", key)
}
if len(m) == 0 {
return nil, nil
}
return m, nil
}
type itemType string
const (
itemText itemType = "TEXT"
itemEqual = "EQUAL"
itemError = "ERROR"
)
func (t itemType) String() string {
return string(t)
}
type state string
const (
// lexer states
stateStart state = "start"
stateEqual = "equal"
stateText = "text"
stateQText = "qtext"
stateQTextEnd = "qtextend"
stateQTextEsc = "qtextesc"
// parser states
stateKey = "key"
stateVal = "val"
)
func lex(s []rune) (itemType, string, int) {
isEqual := func(r rune) bool { return r == '=' }
isEscape := func(r rune) bool { return r == '\\' }
isQuote := func(r rune) bool { return r == '"' }
isSpace := func(r rune) bool { return r == ' ' }
unquote := func(r []rune) (string, error) {
v := strings.TrimSpace(string(r))
return strconv.Unquote(v)
}
var quote rune
state := stateStart
for i, r := range s {
// fmt.Println("lex:", "i:", i, "r:", string(r), "state:", string(state), "head:", string(s[:i]), "tail:", string(s[i:]))
switch state {
case stateStart:
switch {
case isSpace(r):
// state = stateStart
case isEqual(r):
state = stateEqual
case isQuote(r):
quote = r
state = stateQText
default:
state = stateText
}
case stateEqual:
return itemEqual, "", i
case stateText:
switch {
case isEqual(r) || isSpace(r):
v := strings.TrimSpace(string(s[:i]))
return itemText, v, i
default:
// state = stateText
}
case stateQText:
switch {
case r == quote:
state = stateQTextEnd
case isEscape(r):
state = stateQTextEsc
default:
// state = stateQText
}
case stateQTextEsc:
state = stateQText
case stateQTextEnd:
v, err := unquote(s[:i])
if err != nil {
return itemError, err.Error(), i
}
return itemText, v, i
}
}
// fmt.Println("lex:", "state:", string(state))
switch state {
case stateEqual:
return itemEqual, "", len(s)
case stateQText:
return itemError, "unbalanced quotes", len(s)
case stateQTextEsc:
return itemError, "unterminated escape sequence", len(s)
case stateQTextEnd:
v, err := unquote(s)
if err != nil {
return itemError, err.Error(), len(s)
}
return itemText, v, len(s)
default:
return itemText, strings.TrimSpace(string(s)), len(s)
}
}

2
vendor/vendor.json vendored
View File

@ -25,7 +25,7 @@
{"path":"github.com/hashicorp/errwrap","checksumSHA1":"cdOCt0Yb+hdErz8NAQqayxPmRsY=","revision":"7554cd9344cec97297fa6649b055a8c98c2a1e55","revisionTime":"2014-10-28T05:47:10Z"}, {"path":"github.com/hashicorp/errwrap","checksumSHA1":"cdOCt0Yb+hdErz8NAQqayxPmRsY=","revision":"7554cd9344cec97297fa6649b055a8c98c2a1e55","revisionTime":"2014-10-28T05:47:10Z"},
{"path":"github.com/hashicorp/go-checkpoint","checksumSHA1":"nd3S1qkFv7zZxA9be0bw4nT0pe0=","revision":"e4b2dc34c0f698ee04750bf2035d8b9384233e1b","revisionTime":"2015-10-22T18:15:14Z"}, {"path":"github.com/hashicorp/go-checkpoint","checksumSHA1":"nd3S1qkFv7zZxA9be0bw4nT0pe0=","revision":"e4b2dc34c0f698ee04750bf2035d8b9384233e1b","revisionTime":"2015-10-22T18:15:14Z"},
{"path":"github.com/hashicorp/go-cleanhttp","checksumSHA1":"b8F628srIitj5p7Y130xc9k0QWs=","revision":"3573b8b52aa7b37b9358d966a898feb387f62437","revisionTime":"2017-02-11T01:34:15Z"}, {"path":"github.com/hashicorp/go-cleanhttp","checksumSHA1":"b8F628srIitj5p7Y130xc9k0QWs=","revision":"3573b8b52aa7b37b9358d966a898feb387f62437","revisionTime":"2017-02-11T01:34:15Z"},
{"path":"github.com/hashicorp/go-discover","checksumSHA1":"uNoQWG5h2hzWHjaLi376ZXVaCr4=","revision":"21b26b722865b64ae5809a532d2c18f3a3800129","revisionTime":"2017-08-16T16:53:52Z"}, {"path":"github.com/hashicorp/go-discover","checksumSHA1":"tWQmb13hmUBoWi0ZHnc4CLBqRc0=","revision":"745a463b035b25a4d0c12d056e208e3850fdd1be","revisionTime":"2017-10-04T16:46:45Z"},
{"path":"github.com/hashicorp/go-discover/provider/aws","checksumSHA1":"lyPRg8aZKgGiNkMILk/VKwOqMy4=","revision":"25e4565347de14cea0a0e0730374c9fcffa7bab0","revisionTime":"2017-09-25T01:06:15Z","tree":true}, {"path":"github.com/hashicorp/go-discover/provider/aws","checksumSHA1":"lyPRg8aZKgGiNkMILk/VKwOqMy4=","revision":"25e4565347de14cea0a0e0730374c9fcffa7bab0","revisionTime":"2017-09-25T01:06:15Z","tree":true},
{"path":"github.com/hashicorp/go-discover/provider/azure","checksumSHA1":"r97P32e+VmNMh2vwLkZa1zPEDQU=","revision":"b518491d039b6782035b8881502b4f5e9fcc887b","revisionTime":"2017-08-01T15:32:04Z","tree":true}, {"path":"github.com/hashicorp/go-discover/provider/azure","checksumSHA1":"r97P32e+VmNMh2vwLkZa1zPEDQU=","revision":"b518491d039b6782035b8881502b4f5e9fcc887b","revisionTime":"2017-08-01T15:32:04Z","tree":true},
{"path":"github.com/hashicorp/go-discover/provider/gce","checksumSHA1":"KC/MepQsQF17904UShiM61jmaEs=","revision":"b518491d039b6782035b8881502b4f5e9fcc887b","revisionTime":"2017-08-01T15:32:04Z","tree":true}, {"path":"github.com/hashicorp/go-discover/provider/gce","checksumSHA1":"KC/MepQsQF17904UShiM61jmaEs=","revision":"b518491d039b6782035b8881502b4f5e9fcc887b","revisionTime":"2017-08-01T15:32:04Z","tree":true},