From c409f939989546871db3dcd27ebb3f50564727b3 Mon Sep 17 00:00:00 2001 From: Steffen Siering Date: Tue, 29 Nov 2016 15:49:26 +0100 Subject: [PATCH] Update go-ucfg (#3045) - Update go-ucfg - add support for parsing lists/dictionaries from environment variables and via `-E` flag --- CHANGELOG.asciidoc | 2 + glide.yaml | 2 +- heartbeat/monitors/util.go | 12 +- heartbeat/scheduler/schedule/cron/cron.go | 16 +- heartbeat/scheduler/schedule/schedule.go | 16 +- libbeat/outputs/outil/select_test.go | 76 ++--- libbeat/outputs/tls.go | 15 +- libbeat/outputs/transport/tls.go | 7 +- .../cassandra/internal/gocql/marshal.go | 18 +- .../github.com/elastic/go-ucfg/CHANGELOG.md | 27 +- vendor/github.com/elastic/go-ucfg/doc.go | 10 + vendor/github.com/elastic/go-ucfg/error.go | 45 ++- .../github.com/elastic/go-ucfg/flag/value.go | 36 +-- vendor/github.com/elastic/go-ucfg/getset.go | 133 +++++++- .../elastic/go-ucfg/internal/parse/parse.go | 302 ++++++++++++++++++ vendor/github.com/elastic/go-ucfg/merge.go | 47 +++ vendor/github.com/elastic/go-ucfg/opts.go | 53 ++- vendor/github.com/elastic/go-ucfg/reify.go | 126 ++++++-- vendor/github.com/elastic/go-ucfg/types.go | 119 ++++++- vendor/github.com/elastic/go-ucfg/ucfg.go | 31 +- vendor/github.com/elastic/go-ucfg/unpack.go | 165 ++++++++++ vendor/github.com/elastic/go-ucfg/util.go | 34 -- .../github.com/elastic/go-ucfg/validator.go | 27 +- .../github.com/elastic/go-ucfg/variables.go | 4 +- 24 files changed, 1065 insertions(+), 258 deletions(-) create mode 100644 vendor/github.com/elastic/go-ucfg/doc.go create mode 100644 vendor/github.com/elastic/go-ucfg/internal/parse/parse.go create mode 100644 vendor/github.com/elastic/go-ucfg/unpack.go diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index f4192ac0292..5b4bcd961e8 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -54,6 +54,8 @@ https://github.com/elastic/beats/compare/v5.0.1...master[Check the HEAD diff] *Affecting all Beats* - Add add_cloud_metadata processor for collecting cloud provider metadata. {pull}2728[2728] - Added decode_json_fields processor for decoding fields containing JSON strings. {pull}2605[2605] +- Add support for passing list and dictionary settings via -E flag. +- Support for parsing list and dictionary setting from environment variables. *Metricbeat* diff --git a/glide.yaml b/glide.yaml index d86cbf3fbc3..dcfa3088eee 100644 --- a/glide.yaml +++ b/glide.yaml @@ -81,7 +81,7 @@ import: - package: github.com/dustin/go-humanize version: 499693e27ee0d14ffab67c31ad065fdb3d34ea75 - package: github.com/elastic/go-ucfg - version: v0.3.7 + version: v0.4.2 - package: github.com/armon/go-socks5 version: 3a873e99f5400ad7706e464e905ffcc94b3ff247 - package: github.com/pkg/errors diff --git a/heartbeat/monitors/util.go b/heartbeat/monitors/util.go index fe81ab7072f..71f07bff394 100644 --- a/heartbeat/monitors/util.go +++ b/heartbeat/monitors/util.go @@ -281,22 +281,14 @@ func (f funcTask) annotated(start time.Time, typ string) TaskRunner { return annotated(start, typ, f.run) } -func (p *PingMode) Unpack(v interface{}) error { - var fail = errors.New("expecting 'any' or 'all'") - s, ok := v.(string) - if !ok { - return fail - } - +func (p *PingMode) Unpack(s string) error { switch s { case "all": *p = PingAll case "any": *p = PingAny - default: - return fail } - return nil + return errors.New("expecting 'any' or 'all'") } func annotated(start time.Time, typ string, fn func() (common.MapStr, []TaskRunner, error)) TaskRunner { diff --git a/heartbeat/scheduler/schedule/cron/cron.go b/heartbeat/scheduler/schedule/cron/cron.go index 1bef22e2fc6..e7ade343bc7 100644 --- a/heartbeat/scheduler/schedule/cron/cron.go +++ b/heartbeat/scheduler/schedule/cron/cron.go @@ -1,7 +1,6 @@ package cron import ( - "errors" "time" "github.com/gorhill/cronexpr" @@ -27,17 +26,10 @@ func (s *Schedule) Next(t time.Time) time.Time { return expr.Next(t) } -func (s *Schedule) Unpack(in interface{}) error { - str, ok := in.(string) - if !ok { - return errors.New("scheduler string required") - } - +func (s *Schedule) Unpack(str string) error { tmp, err := Parse(str) - if err != nil { - return err + if err == nil { + *s = *tmp } - - *s = *tmp - return nil + return err } diff --git a/heartbeat/scheduler/schedule/schedule.go b/heartbeat/scheduler/schedule/schedule.go index 7b77ee929b4..1d8a47d4462 100644 --- a/heartbeat/scheduler/schedule/schedule.go +++ b/heartbeat/scheduler/schedule/schedule.go @@ -1,7 +1,6 @@ package schedule import ( - "errors" "strings" "time" @@ -43,17 +42,10 @@ func (s intervalScheduler) Next(t time.Time) time.Time { return t.Add(s.interval) } -func (s *Schedule) Unpack(in interface{}) error { - str, ok := in.(string) - if !ok { - return errors.New("scheduler string required") - } - +func (s *Schedule) Unpack(str string) error { tmp, err := Parse(str) - if err != nil { - return err + if err == nil { + *s = *tmp } - - *s = *tmp - return nil + return err } diff --git a/libbeat/outputs/outil/select_test.go b/libbeat/outputs/outil/select_test.go index 369c1c83d37..41a1ddbf33f 100644 --- a/libbeat/outputs/outil/select_test.go +++ b/libbeat/outputs/outil/select_test.go @@ -1,6 +1,7 @@ package outil import ( + "strings" "testing" "github.com/elastic/beats/libbeat/common" @@ -49,24 +50,24 @@ func TestSelector(t *testing.T) { { "missing format string key with default in rule", `keys: - - key: '%{[key]}' - default: value`, + - key: '%{[key]}' + default: value`, common.MapStr{}, "value", }, { "empty format string key with default in rule", `keys: - - key: '%{[key]}' - default: value`, + - key: '%{[key]}' + default: value`, common.MapStr{"key": ""}, "value", }, { "missing format string key with constant in next rule", `keys: - - key: '%{[key]}' - - key: value`, + - key: '%{[key]}' + - key: value`, common.MapStr{}, "value", }, @@ -79,83 +80,83 @@ func TestSelector(t *testing.T) { { "apply mapping", `keys: - - key: '%{[key]}' - mappings: - v: value`, + - key: '%{[key]}' + mappings: + v: value`, common.MapStr{"key": "v"}, "value", }, { "apply mapping with default on empty key", `keys: - - key: '%{[key]}' - default: value - mappings: - v: 'v'`, + - key: '%{[key]}' + default: value + mappings: + v: 'v'`, common.MapStr{"key": ""}, "value", }, { "apply mapping with default on empty lookup", `keys: - - key: '%{[key]}' - default: value - mappings: - v: ''`, + - key: '%{[key]}' + default: value + mappings: + v: ''`, common.MapStr{"key": "v"}, "value", }, { "apply mapping without match", `keys: - - key: '%{[key]}' - mappings: - v: '' - - key: value`, + - key: '%{[key]}' + mappings: + v: '' + - key: value`, common.MapStr{"key": "x"}, "value", }, { "mapping with constant key", `keys: - - key: k - mappings: - k: value`, + - key: k + mappings: + k: value`, common.MapStr{}, "value", }, { "mapping with missing constant key", `keys: - - key: unknown - mappings: {k: wrong} - - key: value`, + - key: unknown + mappings: {k: wrong} + - key: value`, common.MapStr{}, "value", }, { "mapping with missing constant key, but default", `keys: - - key: unknown - default: value - mappings: {k: wrong}`, + - key: unknown + default: value + mappings: {k: wrong}`, common.MapStr{}, "value", }, { "matching condition", `keys: - - key: value - when.equals.test: test`, + - key: value + when.equals.test: test`, common.MapStr{"test": "test"}, "value", }, { "failing condition", `keys: - - key: wrong - when.equals.test: test - - key: value`, + - key: wrong + when.equals.test: test + - key: value`, common.MapStr{"test": "x"}, "value", }, @@ -164,9 +165,10 @@ func TestSelector(t *testing.T) { for i, test := range tests { t.Logf("run (%v): %v", i, test.title) - cfg, err := common.NewConfigWithYAML([]byte(test.config), "test") + yaml := strings.Replace(test.config, "\t", " ", -1) + cfg, err := common.NewConfigWithYAML([]byte(yaml), "test") if err != nil { - t.Error(err) + t.Errorf("YAML parse error: %v\n%v", err, yaml) continue } diff --git a/libbeat/outputs/tls.go b/libbeat/outputs/tls.go index d8915f47ec0..79fd297a2eb 100644 --- a/libbeat/outputs/tls.go +++ b/libbeat/outputs/tls.go @@ -269,12 +269,7 @@ func loadCertificateAuthorities(CAs []string) (*x509.CertPool, []error) { return roots, errors } -func (cs *tlsCipherSuite) Unpack(in interface{}) error { - s, ok := in.(string) - if !ok { - return fmt.Errorf("tls cipher suite must be an identifier") - } - +func (cs *tlsCipherSuite) Unpack(s string) error { suite, found := tlsCipherSuites[s] if !found { return fmt.Errorf("invalid tls cipher suite '%v'", s) @@ -284,12 +279,7 @@ func (cs *tlsCipherSuite) Unpack(in interface{}) error { return nil } -func (ct *tlsCurveType) Unpack(in interface{}) error { - s, ok := in.(string) - if !ok { - return fmt.Errorf("tls curve type must be an identifier") - } - +func (ct *tlsCurveType) Unpack(s string) error { t, found := tlsCurveTypes[s] if !found { return fmt.Errorf("invalid tls curve type '%v'", s) @@ -297,5 +287,4 @@ func (ct *tlsCurveType) Unpack(in interface{}) error { *ct = t return nil - } diff --git a/libbeat/outputs/transport/tls.go b/libbeat/outputs/transport/tls.go index ef6dc9b0404..fff921ccb74 100644 --- a/libbeat/outputs/transport/tls.go +++ b/libbeat/outputs/transport/tls.go @@ -234,12 +234,7 @@ func (v TLSVersion) String() string { return "unknown" } -func (v *TLSVersion) Unpack(in interface{}) error { - s, ok := in.(string) - if !ok { - return fmt.Errorf("tls version must be an identifier") - } - +func (v *TLSVersion) Unpack(s string) error { version, found := tlsProtocolVersions[s] if !found { return fmt.Errorf("invalid tls version '%v'", s) diff --git a/packetbeat/protos/cassandra/internal/gocql/marshal.go b/packetbeat/protos/cassandra/internal/gocql/marshal.go index a8d3f460502..9a7d8ff8ef8 100644 --- a/packetbeat/protos/cassandra/internal/gocql/marshal.go +++ b/packetbeat/protos/cassandra/internal/gocql/marshal.go @@ -12,9 +12,10 @@ import ( "time" "errors" + "strings" + "github.com/elastic/beats/libbeat/logp" "gopkg.in/inf.v0" - "strings" ) // TypeInfo describes a Cassandra specific data type. @@ -530,19 +531,12 @@ func FrameOpFromString(s string) (FrameOp, error) { return op, nil } -func (f *FrameOp) Unpack(in interface{}) error { - s, ok := in.(string) - if !ok { - return errors.New("expected string") - } - +func (f *FrameOp) Unpack(s string) error { op, err := FrameOpFromString(s) - if err != nil { - return err + if err == nil { + *f = op } - - *f = op - return nil + return err } const ( diff --git a/vendor/github.com/elastic/go-ucfg/CHANGELOG.md b/vendor/github.com/elastic/go-ucfg/CHANGELOG.md index 1d5c8942987..6f17906b3f6 100644 --- a/vendor/github.com/elastic/go-ucfg/CHANGELOG.md +++ b/vendor/github.com/elastic/go-ucfg/CHANGELOG.md @@ -14,6 +14,28 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +## [0.4.2] + +### Fixed +- Treat `,` character as only special character in non quoted top-level strings. #78 + +## [0.4.1] + +### Fixed +- Fix parsing empty string or nil objects from environment variables. #76 + +## [0.4.0] + +### Added +- Syntax for passing lists and dictionaries to flags. #72 +- Add Unpacker interface specializations for primitive types. #73 +- Variable expansion parsing lists and dictionaries with parser introduced in + #72. #74 + +### Fixed +- Fix Unpacker interface not applied if some 'old' value is already present on + target and is struct implementing Unpack. #73 + ## [0.3.7] ### Fixed @@ -118,7 +140,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Introduced CHANGELOG.md for documenting changes to ucfg. -[Unreleased]: https://github.com/elastic/go-ucfg/compare/v0.3.7...HEAD +[Unreleased]: https://github.com/elastic/go-ucfg/compare/v0.4.2...HEAD +[0.4.2]: https://github.com/elastic/go-ucfg/compare/v0.4.1...v0.4.2 +[0.4.1]: https://github.com/elastic/go-ucfg/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/elastic/go-ucfg/compare/v0.3.7...v0.4.0 [0.3.7]: https://github.com/elastic/go-ucfg/compare/v0.3.6...v0.3.7 [0.3.6]: https://github.com/elastic/go-ucfg/compare/v0.3.5...v0.3.6 [0.3.5]: https://github.com/elastic/go-ucfg/compare/v0.3.4...v0.3.5 diff --git a/vendor/github.com/elastic/go-ucfg/doc.go b/vendor/github.com/elastic/go-ucfg/doc.go new file mode 100644 index 00000000000..73c6d1eeb2a --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/doc.go @@ -0,0 +1,10 @@ +// Package ucfg provides a common representation for hierarchical configurations. +// +// The common representation provided by the Config type can be used with different +// configuration file formats like XML, JSON, HSJSON, YAML, or TOML. +// +// Config provides a low level and a high level interface for reading settings +// with additional features like custom unpackers, validation and capturing +// sub-configurations for deferred interpretation, lazy intra-configuration +// variable expansion, and OS environment variable expansion. +package ucfg diff --git a/vendor/github.com/elastic/go-ucfg/error.go b/vendor/github.com/elastic/go-ucfg/error.go index de8ef410357..05f989c8912 100644 --- a/vendor/github.com/elastic/go-ucfg/error.go +++ b/vendor/github.com/elastic/go-ucfg/error.go @@ -7,13 +7,18 @@ import ( "runtime/debug" ) +// Error type returned by all public functions in go-ucfg. type Error interface { error - Reason() error // error class, one of ErrConfig, ErrImplementation, ErrUnknown Class() error + // The internal reason error code like ErrMissing, ErrRequired, + // ErrTypeMismatch and others. + Reason() error + + // The error message. Message() string // [optional] path of config element error occurred for @@ -27,6 +32,7 @@ type baseError struct { reason error class error message string + path string } type criticalError struct { @@ -34,13 +40,7 @@ type criticalError struct { trace string } -type pathError struct { - baseError - meta *Meta - path string -} - -// error Reasons +// Error Reasons var ( ErrMissing = errors.New("missing field") @@ -79,28 +79,30 @@ var ( ErrEmpty = errors.New("empty field") ) -// error classes +// Error Classes var ( ErrConfig = errors.New("Configuration error") ErrImplementation = errors.New("Implementation error") ErrUnknown = errors.New("Unspecified") ) -func (e baseError) Message() string { return e.Error() } -func (e baseError) Reason() error { return e.reason } -func (e baseError) Class() error { return e.class } -func (e baseError) Trace() string { return "" } -func (e baseError) Path() string { return "" } +func (e baseError) Error() string { return e.Message() } +func (e baseError) Reason() error { return e.reason } +func (e baseError) Class() error { return e.class } +func (e baseError) Trace() string { return "" } +func (e baseError) Path() string { return e.path } -func (e baseError) Error() string { +func (e baseError) Message() string { if e.message == "" { return e.reason.Error() } return e.message } +func (e criticalError) Trace() string { return e.trace } + func (e criticalError) Error() string { - return fmt.Sprintf("%s\nTrace:%v\n", e.baseError, e.trace) + return fmt.Sprintf("%s\nTrace:%v\n", e.baseError.Message(), e.trace) } func raiseErr(reason error, message string) Error { @@ -126,21 +128,14 @@ func raiseCritical(reason error, message string) Error { message = fmt.Sprintf("(assert) %v", message) } return criticalError{ - baseError{reason, ErrImplementation, message}, + baseError{reason, ErrImplementation, message, ""}, string(debug.Stack()), } } func raisePathErr(reason error, meta *Meta, message, path string) Error { - // fmt.Printf("path err, reason='%v', meta=%v, message='%v', path='%v'\n", reason, meta, message, path) message = messagePath(reason, meta, message, path) - // fmt.Printf(" -> report message: %v\n", message) - - return pathError{ - baseError{reason, ErrConfig, message}, - meta, - path, - } + return baseError{reason, ErrConfig, message, path} } func messageMeta(message string, meta *Meta) string { diff --git a/vendor/github.com/elastic/go-ucfg/flag/value.go b/vendor/github.com/elastic/go-ucfg/flag/value.go index ddb4bc3516f..c82307f0be7 100644 --- a/vendor/github.com/elastic/go-ucfg/flag/value.go +++ b/vendor/github.com/elastic/go-ucfg/flag/value.go @@ -2,10 +2,10 @@ package flag import ( "fmt" - "strconv" "strings" "github.com/elastic/go-ucfg" + "github.com/elastic/go-ucfg/internal/parse" ) // NewFlagKeyValue implements the flag.Value interface for @@ -32,6 +32,7 @@ func NewFlagKeyValue(cfg *ucfg.Config, autoBool bool, opts ...ucfg.Option) *Flag return newFlagValue(cfg, opts, func(arg string) (*ucfg.Config, error, error) { var key string var val interface{} + var err error args := strings.SplitN(arg, "=", 2) if len(args) < 2 { @@ -44,7 +45,10 @@ func NewFlagKeyValue(cfg *ucfg.Config, autoBool bool, opts ...ucfg.Option) *Flag val = true } else { key = args[0] - val = parseCLIValue(args[1]) + val, err = parse.ParseValue(args[1]) + if err != nil { + return nil, err, err + } } tmp := map[string]interface{}{key: val} @@ -52,31 +56,3 @@ func NewFlagKeyValue(cfg *ucfg.Config, autoBool bool, opts ...ucfg.Option) *Flag return cfg, err, err }) } - -func parseCLIValue(value string) interface{} { - if b, ok := parseBoolValue(value); ok { - return b - } - - if n, err := strconv.ParseUint(value, 0, 64); err == nil { - return n - } - if n, err := strconv.ParseInt(value, 0, 64); err == nil { - return n - } - if n, err := strconv.ParseFloat(value, 64); err == nil { - return n - } - - return value -} - -func parseBoolValue(str string) (value bool, ok bool) { - switch str { - case "1", "t", "T", "true", "TRUE", "True", "on", "ON": - return true, true - case "0", "f", "F", "false", "FALSE", "False", "off", "OFF": - return false, true - } - return false, false -} diff --git a/vendor/github.com/elastic/go-ucfg/getset.go b/vendor/github.com/elastic/go-ucfg/getset.go index e41db69a3dd..518fa2c82c5 100644 --- a/vendor/github.com/elastic/go-ucfg/getset.go +++ b/vendor/github.com/elastic/go-ucfg/getset.go @@ -1,7 +1,7 @@ package ucfg // ****************************************************************************** -// Low level getters and setters (do we actually need this?) +// Low level getters and setters // ****************************************************************************** func convertErr(opts *options, v value, err error, to string) Error { @@ -11,8 +11,12 @@ func convertErr(opts *options, v value, err error, to string) Error { return raiseConversion(opts, v, err, to) } -// number of elements for this field. If config value is a list, returns number -// of elements in list +// CountField returns number of entries in a table or 1 if entry is a primitive value. +// Primitives settings can be handled like a list with 1 entry. +// +// If name is empty, the total number of top-level settings is returned. +// +// CountField supports the options: PathSep, Env, Resolve, ResolveEnv func (c *Config) CountField(name string, opts ...Option) (int, error) { if name == "" { return len(c.fields.array()) + len(c.fields.dict()), nil @@ -24,6 +28,16 @@ func (c *Config) CountField(name string, opts ...Option) (int, error) { return -1, raiseMissing(c, name) } +// Bool reads a boolean setting returning an error if the setting has no +// boolean value. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Bool supports the options: PathSep, Env, Resolve, ResolveEnv func (c *Config) Bool(name string, idx int, opts ...Option) (bool, error) { O := makeOptions(opts) v, err := c.getField(name, idx, O) @@ -34,6 +48,16 @@ func (c *Config) Bool(name string, idx int, opts ...Option) (bool, error) { return b, convertErr(O, v, fail, "bool") } +// Strings reads a string setting returning an error if the setting has +// no string or primitive value convertible to string. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// String supports the options: PathSep, Env, Resolve, ResolveEnv func (c *Config) String(name string, idx int, opts ...Option) (string, error) { O := makeOptions(opts) v, err := c.getField(name, idx, O) @@ -44,16 +68,39 @@ func (c *Config) String(name string, idx int, opts ...Option) (string, error) { return s, convertErr(O, v, fail, "string") } +// Int reads an int64 value returning an error if the setting is +// not integer value, the primitive value is not convertible to int or a conversion +// would create an integer overflow. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Int supports the options: PathSep, Env, Resolve, ResolveEnv func (c *Config) Int(name string, idx int, opts ...Option) (int64, error) { O := makeOptions(opts) v, err := c.getField(name, idx, O) if err != nil { return 0, err } + i, fail := v.toInt(O) return i, convertErr(O, v, fail, "int") } +// Uint reads an uint64 value returning an error if the setting is +// not unsigned value, the primitive value is not convertible to uint64 or a conversion +// would create an integer overflow. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Uint supports the options: PathSep, Env, Resolve, ResolveEnv func (c *Config) Uint(name string, idx int, opts ...Option) (uint64, error) { O := makeOptions(opts) v, err := c.getField(name, idx, O) @@ -64,6 +111,16 @@ func (c *Config) Uint(name string, idx int, opts ...Option) (uint64, error) { return u, convertErr(O, v, fail, "uint") } +// Float reads a float64 value returning an error if the setting is +// not a float value or the primitive value is not convertible to float. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Float supports the options: PathSep, Env, Resolve, ResolveEnv func (c *Config) Float(name string, idx int, opts ...Option) (float64, error) { O := makeOptions(opts) v, err := c.getField(name, idx, O) @@ -74,6 +131,16 @@ func (c *Config) Float(name string, idx int, opts ...Option) (float64, error) { return f, convertErr(O, v, fail, "float") } +// Child returns a child configuration or an error if the setting requested is a +// primitive value only. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Child supports the options: PathSep, Env, Resolve, ResolveEnv func (c *Config) Child(name string, idx int, opts ...Option) (*Config, error) { O := makeOptions(opts) v, err := c.getField(name, idx, O) @@ -84,30 +151,89 @@ func (c *Config) Child(name string, idx int, opts ...Option) (*Config, error) { return c, convertErr(O, v, fail, "object") } +// SetBool sets a boolean primitive value. An error is returned if the new name +// is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetBool supports the options: PathSep, MetaData func (c *Config) SetBool(name string, idx int, value bool, opts ...Option) error { return c.setField(name, idx, &cfgBool{b: value}, opts) } +// SetInt sets an integer primitive value. An error is returned if the new +// name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetInt supports the options: PathSep, MetaData func (c *Config) SetInt(name string, idx int, value int64, opts ...Option) error { return c.setField(name, idx, &cfgInt{i: value}, opts) } +// SetUint sets an unsigned integer primitive value. An error is returned if +// the name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetUint supports the options: PathSep, MetaData func (c *Config) SetUint(name string, idx int, value uint64, opts ...Option) error { return c.setField(name, idx, &cfgUint{u: value}, opts) } +// SetFloat sets an floating point primitive value. An error is returned if +// the name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetFloat supports the options: PathSep, MetaData func (c *Config) SetFloat(name string, idx int, value float64, opts ...Option) error { return c.setField(name, idx, &cfgFloat{f: value}, opts) } +// SetString sets string value. An error is returned if the name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetString supports the options: PathSep, MetaData func (c *Config) SetString(name string, idx int, value string, opts ...Option) error { return c.setField(name, idx, &cfgString{s: value}, opts) } +// SetChild adds a sub-configuration. An error is returned if the name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetChild supports the options: PathSep, MetaData func (c *Config) SetChild(name string, idx int, value *Config, opts ...Option) error { return c.setField(name, idx, cfgSub{c: value}, opts) } +// getField supports the options: PathSep, Env, Resolve, ResolveEnv func (c *Config) getField(name string, idx int, opts *options) (value, Error) { p := parsePathIdx(name, opts.pathSep, idx) v, err := p.GetValue(c, opts) @@ -121,6 +247,7 @@ func (c *Config) getField(name string, idx int, opts *options) (value, Error) { return v, nil } +// setField supports the options: PathSep, MetaData func (c *Config) setField(name string, idx int, v value, options []Option) Error { opts := makeOptions(options) p := parsePathIdx(name, opts.pathSep, idx) diff --git a/vendor/github.com/elastic/go-ucfg/internal/parse/parse.go b/vendor/github.com/elastic/go-ucfg/internal/parse/parse.go new file mode 100644 index 00000000000..a726078ff78 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/internal/parse/parse.go @@ -0,0 +1,302 @@ +package parse + +import ( + "errors" + "fmt" + "strconv" + "strings" + "unicode" +) + +type flagParser struct { + input string +} + +// ParseValue parses command line arguments, supporting +// boolean, numbers, strings, arrays, objects. +// +// The parser implements a superset of JSON, but only a subset of YAML by +// allowing for arrays and objects having a trailing comma. In addition 3 +// strings types are supported: +// +// 1. single quoted string (no unescaping of any characters) +// 2. double quoted strings (characters are escaped) +// 3. strings without quotes. String parsing stops in +// special characters like '[]{},:' +// +// In addition, top-level values can be separated by ',' to build arrays +// without having to use []. +func ParseValue(content string) (interface{}, error) { + p := &flagParser{strings.TrimSpace(content)} + v, err := p.parse() + if err != nil { + return nil, fmt.Errorf("%v when parsing '%v'", err.Error(), content) + } + return v, nil +} + +func (p *flagParser) parse() (interface{}, error) { + var values []interface{} + + for { + v, err := p.parseValue(true) + if err != nil { + return nil, err + } + values = append(values, v) + + p.ignoreWhitespace() + if p.input == "" { + break + } + + if err := p.expectChar(','); err != nil { + return nil, err + } + } + + switch len(values) { + case 0: + return nil, nil + case 1: + return values[0], nil + } + return values, nil +} + +func (p *flagParser) parseValue(toplevel bool) (interface{}, error) { + p.ignoreWhitespace() + in := p.input + + if in == "" { + return nil, nil + } + + switch in[0] { + case '[': + return p.parseArray() + case '{': + return p.parseObj() + case '"': + return p.parseStringDQuote() + case '\'': + return p.parseStringSQuote() + default: + return p.parsePrimitive(toplevel) + } +} + +func (p *flagParser) ignoreWhitespace() { + p.input = strings.TrimLeftFunc(p.input, unicode.IsSpace) +} + +func (p *flagParser) parseArray() (interface{}, error) { + p.input = p.input[1:] + + var values []interface{} +loop: + for { + p.ignoreWhitespace() + if p.input[0] == ']' { + p.input = p.input[1:] + break + } + + v, err := p.parseValue(false) + if err != nil { + return nil, err + } + values = append(values, v) + + p.ignoreWhitespace() + if p.input == "" { + return nil, errors.New("array closing ']' missing") + } + + next := p.input[0] + p.input = p.input[1:] + + switch next { + case ']': + break loop + case ',': + continue + default: + return nil, errors.New("array expected ',' or ']'") + } + + } + + if len(values) == 0 { + return nil, nil + } + + return values, nil +} + +func (p *flagParser) parseObj() (interface{}, error) { + p.input = p.input[1:] + + O := map[string]interface{}{} + +loop: + for { + p.ignoreWhitespace() + if p.input[0] == '}' { + p.input = p.input[1:] + break + } + + k, err := p.parseKey() + if err != nil { + return nil, err + } + + p.ignoreWhitespace() + if err := p.expectChar(':'); err != nil { + return nil, err + } + + v, err := p.parseValue(false) + if err != nil { + return nil, err + } + + if p.input == "" { + return nil, errors.New("dictionary expected ',' or '}'") + } + + O[k] = v + next := p.input[0] + p.input = p.input[1:] + + switch next { + case '}': + break loop + case ',': + continue + default: + return nil, errors.New("dictionary expected ',' or '}'") + } + } + + // empty object + if len(O) == 0 { + return nil, nil + } + + return O, nil +} + +func (p *flagParser) parseKey() (string, error) { + in := p.input + if in == "" { + return "", errors.New("expected key") + } + + switch in[0] { + case '"': + return p.parseStringDQuote() + case '\'': + return p.parseStringSQuote() + default: + return p.parseNonQuotedString(false) + } +} + +func (p *flagParser) parseStringDQuote() (string, error) { + in := p.input + off := 1 + var i int + for { + i = strings.IndexByte(in[off:], '"') + if i < 0 { + return "", errors.New("Missing \" to close string ") + } + + i += off + if in[i-1] != '\\' { + break + } + off = i + 1 + } + + p.input = in[i+1:] + return strconv.Unquote(in[:i+1]) +} + +func (p *flagParser) parseStringSQuote() (string, error) { + in := p.input + i := strings.IndexByte(in[1:], '\'') + if i < 0 { + return "", errors.New("missing ' to close string") + } + + p.input = in[i+2:] + return in[1 : 1+i], nil +} + +func (p *flagParser) parseNonQuotedString(toplevel bool) (string, error) { + in := p.input + stopChars := ",:[]{}" + if toplevel { + stopChars = "," + } + idx := strings.IndexAny(in, stopChars) + + if idx == 0 { + return "", fmt.Errorf("unexpected '%v'", string(in[idx])) + } + + content, in := in, "" + if idx > 0 { + content, in = content[:idx], content[idx:] + } + p.input = in + + return strings.TrimSpace(content), nil +} + +func (p *flagParser) parsePrimitive(toplevel bool) (interface{}, error) { + content, err := p.parseNonQuotedString(toplevel) + if err != nil { + return nil, err + } + + if content == "null" { + return nil, nil + } + if b, ok := parseBoolValue(content); ok { + return b, nil + } + if n, err := strconv.ParseUint(content, 0, 64); err == nil { + return n, nil + } + if n, err := strconv.ParseInt(content, 0, 64); err == nil { + return n, nil + } + if n, err := strconv.ParseFloat(content, 64); err == nil { + return n, nil + } + + return content, nil +} + +func (p *flagParser) expectChar(c byte) error { + if p.input == "" || p.input[0] != c { + return fmt.Errorf("expected '%v'", string(c)) + } + + p.input = p.input[1:] + return nil +} + +func parseBoolValue(str string) (value bool, ok bool) { + switch str { + case "t", "T", "true", "TRUE", "True", "on", "ON": + return true, true + case "f", "F", "false", "FALSE", "False", "off", "OFF": + return false, true + } + return false, false +} diff --git a/vendor/github.com/elastic/go-ucfg/merge.go b/vendor/github.com/elastic/go-ucfg/merge.go index b1851c78202..f6611633abb 100644 --- a/vendor/github.com/elastic/go-ucfg/merge.go +++ b/vendor/github.com/elastic/go-ucfg/merge.go @@ -7,6 +7,46 @@ import ( "time" ) +// Merge a map, a slice, a struct or another Config object into c. +// +// Merge traverses the value from recursively copying all values into a hierarchy +// of Config objects plus primitives into c. +// +// Merge supports the options: PathSep, MetaData, StructTag, VarExp +// +// Merge uses the type-dependent default encodings: +// - Boolean values are encoded as booleans. +// - Integer are encoded as int64 values, unsigned integer values as uint64 and +// floats as float64 values. +// - Strings are copied into string values. +// If the VarExp is set, string fields will be parsed into +// variable expansion expressions. The expression can reference any +// other setting by absolute name. +// - Array and slices are copied into new Config objects with index accessors only. +// - Struct values and maps with key type string are encoded as Config objects with +// named field accessors. +// - Config objects will be copied and added to the current hierarchy. +// +// The `config` struct tag (configurable via StructTag option) can be used to +// set the field name and enable additional merging settings per field: +// +// // field appears in Config as key "myName" +// Field int `config:"myName"` +// +// // field appears in sub-Config "mySub" as key "myName" (requires PathSep(".")) +// Field int `config:"mySub.myName"` +// +// // field is processed as if keys are part of outer struct (type can be a +// // struct, a slice, an array, a map or of type *Config) +// Field map[string]interface{} `config:",inline"` +// +// +// Returns an error if merging fails to normalize and validate the from value. +// If duplicate setting names are detected in the input, merging fails as well. +// +// Config cannot represent cyclic structures and Merge does not handle them +// well. Passing cyclic structures to Merge will result in an infinite recursive +// loop. func (c *Config) Merge(from interface{}, options ...Option) error { opts := makeOptions(options) other, err := normalize(opts, from) @@ -128,6 +168,13 @@ func normalize(opts *options, from interface{}) (*Config, Error) { return normalizeStruct(opts, vFrom) case reflect.Map: return normalizeMap(opts, vFrom) + case reflect.Array, reflect.Slice: + tmp, err := normalizeArray(opts, tagOptions{}, context{}, vFrom) + if err != nil { + return nil, err + } + c, _ := tmp.toConfig(opts) + return c, nil } } diff --git a/vendor/github.com/elastic/go-ucfg/opts.go b/vendor/github.com/elastic/go-ucfg/opts.go index 514a480f599..e71f6719cc1 100644 --- a/vendor/github.com/elastic/go-ucfg/opts.go +++ b/vendor/github.com/elastic/go-ucfg/opts.go @@ -2,6 +2,8 @@ package ucfg import "os" +// Option type implementing additional options to be passed +// to go-ucfg library functions. type Option func(*options) type options struct { @@ -12,61 +14,94 @@ type options struct { env []*Config resolvers []func(name string) (string, error) varexp bool + + // temporary cache of parsed splice values for lifetime of call to + // Unpack/Pack/Get/... + parsed map[string]spliceValue +} + +type spliceValue struct { + err error + value value } +// StructTag option sets the struct tag name to use for looking up +// field names and options in `Unpack` and `Merge`. +// The default struct tag in `config`. func StructTag(tag string) Option { return func(o *options) { o.tag = tag } } +// ValidatorTag option sets the struct tag name used to set validators +// on struct fields in `Unpack`. +// The default struct tag in `validate`. func ValidatorTag(tag string) Option { return func(o *options) { o.validatorTag = tag } } +// PathSep sets the path separator used to split up names into a tree like hierarchy. +// If PathSep is not set, field names will not be split. func PathSep(sep string) Option { return func(o *options) { o.pathSep = sep } } +// MetaData option passes additional metadata (currently only source of the +// configuration) to be stored internally (e.g. for error reporting). func MetaData(meta Meta) Option { return func(o *options) { o.meta = &meta } } +// Env option adds another configuration for variable expansion to be used, if +// the path to look up does not exist in the actual configuration. Env can be used +// multiple times in order to add more lookup environments. func Env(e *Config) Option { return func(o *options) { o.env = append(o.env, e) } } +// Resolve option adds a callback used by variable name expansion. The callback +// will be called if a variable can not be resolved from within the actual configuration +// or any of its environments. func Resolve(fn func(name string) (string, error)) Option { return func(o *options) { o.resolvers = append(o.resolvers, fn) } } -var ResolveEnv = Resolve(func(name string) (string, error) { - value := os.Getenv(name) - if value == "" { - return "", ErrMissing - } - return value, nil -}) +// ResolveEnv option adds a look up callback looking up values in the available +// OS environment variables. +var ResolveEnv Option = doResolveEnv -var VarExp Option = func(o *options) { - o.varexp = true +func doResolveEnv(o *options) { + o.resolvers = append(o.resolvers, func(name string) (string, error) { + value := os.Getenv(name) + if value == "" { + return "", ErrMissing + } + return value, nil + }) } +// VarExp option enables support for variable expansion. Resolve and Env options will only be effective if VarExp is set. +var VarExp Option = doVarExp + +func doVarExp(o *options) { o.varexp = true } + func makeOptions(opts []Option) *options { o := options{ tag: "config", validatorTag: "validate", pathSep: "", // no separator by default + parsed: map[string]spliceValue{}, } for _, opt := range opts { opt(&o) diff --git a/vendor/github.com/elastic/go-ucfg/reify.go b/vendor/github.com/elastic/go-ucfg/reify.go index 65a4a44e0a1..d5dbe83b784 100644 --- a/vendor/github.com/elastic/go-ucfg/reify.go +++ b/vendor/github.com/elastic/go-ucfg/reify.go @@ -6,6 +6,96 @@ import ( "time" ) +// Unpack unpacks c into a struct, a map, or a slice allocating maps, slices, +// and pointers as necessary. +// +// Unpack supports the options: PathSep, StructTag, ValidatorTag, Env, Resolve, +// ResolveEnv. +// +// When unpacking into a value, Unpack first will try to call Unpack if the +// value implements the Unpacker interface. Otherwise, Unpack tries to convert +// the internal value into the target type: +// +// # Primitive types +// +// bool: requires setting of type bool or string which parses into a +// boolean value (true, false, on, off) +// int(8, 16, 32, 64): requires any number type convertible to int or a string +// parsing to int. Fails if the target value would overflow. +// uint(8, 16, 32, 64): requires any number type convertible to int or a string +// parsing to int. Fails if the target value is negative or would overflow. +// float(32, 64): requires any number type convertible to float or a string +// parsing to float. Fails if the target value is negative or would overflow. +// string: requires any primitive value which is serialized into a string. +// +// # Special types: +// +// time.Duration: requires a number setting converted to seconds or a string +// parsed into time.Duration via time.ParseDuration. +// *regexp.Regexp: requires a string being compiled into a regular expression +// using regexp.Compile. +// *Config: requires a Config object to be stored by pointer into the target +// value. Can be used to capture a sub-Config without interpreting +// the settings yet. +// +// # Arrays/Slices: +// +// Requires a Config object with indexed entries. Named entries will not be +// unpacked into the Array/Slice. Primitive values will be handled like arrays +// of length 1. +// +// # Map +// +// Requires a Config object with all named top-level entries being unpacked into +// the map. +// +// # Struct +// +// Requires a Config object. All named values in the Config object will be unpacked +// into the struct its fields, if the name is available in the struct. +// A field its name is set using the `config` struct tag (configured by StructTag) +// If tag is missing or no field name is configured in the tag, the field name +// itself will be used. +// +// +// Fields available in a struct or a map, but not in the Config object, will not +// be touched. Default values should be set in the target value before calling Unpack. +// +// Type aliases like "type myTypeAlias T" are unpacked using Unpack if the alias +// implements the Unpacker interface. Otherwise unpacking rules for type T will be used. +// +// When unpacking a value, the Validate method will be called if the value +// implements the Validator interface. Unpacking a struct field the validator +// options will be applied to the unpacked value as well. +// +// Struct field validators are set using the `validate` tag (configurable by +// ValidatorTag). Default validators options are: +// +// required: check value is set and not empty +// nonzero: check numeric value != 0 or string/slice not being empty +// positive: check numeric value >= 0 +// min=: check numeric value >= . If target type is time.Duration, +// can be a duration. +// max=: check numeric value <= . If target type is time.Duration, +// can be a duration. +// +// If a config value is not the convertible to the target type, or overflows the +// target type, Unpack will abort immediately and return the appropriate error. +// +// If validator tags or validation provided by Validate or Unmarshal fails, +// Unpack will abort immediately and return the validate error. +// +// When unpacking into an interface{} value, Unpack will store a value of one of +// these types in the value: +// +// bool for boolean values +// int64 for signed integer values +// uint64 for unsigned integer values +// float64 for floating point values +// string for string values +// []interface{} for list-only Config objects +// map[string]interface{} for Config objects +// nil for pointers if key has a nil value func (c *Config) Unpack(to interface{}, options ...Option) error { opts := makeOptions(options) @@ -98,13 +188,9 @@ func reifyStruct(opts *options, orig reflect.Value, cfg *Config) Error { to.Set(orig) } - if v, ok := implementsUnpacker(to); ok { - reified, err := cfgSub{cfg}.reify(opts) + if v, ok := valueIsUnpacker(to); ok { + err := unpackWith(opts, v, cfgSub{cfg}) if err != nil { - return raisePathErr(err, cfg.metadata, "", cfg.Path(".")) - } - - if err := unpackWith(cfg.ctx, cfg.metadata, v, reified); err != nil { return err } } else { @@ -273,6 +359,15 @@ func reifyMergeValue( } baseType := chaseTypePointers(old.Type()) + + if v, ok := valueIsUnpacker(old); ok { + err := unpackWith(opts.opts, v, val) + if err != nil { + return reflect.Value{}, err + } + return old, nil + } + if tConfig.ConvertibleTo(baseType) { sub, err := val.toConfig(opts.opts) if err != nil { @@ -328,18 +423,6 @@ func reifyMergeValue( return reifySlice(opts, baseType, val) } - if v, ok := implementsUnpacker(old); ok { - reified, err := val.reify(opts.opts) - if err != nil { - ctx := val.Context() - return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path(".")) - } - - if err := unpackWith(val.Context(), val.meta(), v, reified); err != nil { - return reflect.Value{}, err - } - return old, nil - } return reifyPrimitive(opts, val, t, baseType) } @@ -439,13 +522,8 @@ func reifyPrimitive( var ok bool if v, ok = typeIsUnpacker(baseType); ok { - reified, err := val.reify(opts.opts) + err := unpackWith(opts.opts, v, val) if err != nil { - ctx := val.Context() - return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path(".")) - } - - if err := unpackWith(val.Context(), val.meta(), v, reified); err != nil { return reflect.Value{}, err } } else { diff --git a/vendor/github.com/elastic/go-ucfg/types.go b/vendor/github.com/elastic/go-ucfg/types.go index dd96a811f32..2d28da52d7a 100644 --- a/vendor/github.com/elastic/go-ucfg/types.go +++ b/vendor/github.com/elastic/go-ucfg/types.go @@ -5,6 +5,11 @@ import ( "math" "reflect" "strconv" + "strings" + "sync/atomic" + + "github.com/elastic/go-ucfg/internal/parse" + uuid "github.com/satori/go.uuid" ) type value interface { @@ -77,6 +82,14 @@ type cfgRef struct { type cfgSplice struct { cfgPrimitive + + // id used to store intermediate parse results in current execution context. + // As parsing results might differ between multiple calls due to: + // splice being shared between multiple configurations, or environment + // changing between calls + lazy nature of cfgSplice, parsing results cannot + // be stored in cfgSplice itself. + id string + splice varEvaler } @@ -87,6 +100,8 @@ type cfgPrimitive struct { metadata *Meta } +var spliceSeq int32 + func (c *context) empty() bool { return c.parent == nil } @@ -149,7 +164,8 @@ func newRef(ctx context, m *Meta, ref *reference) *cfgRef { } func newSplice(ctx context, m *Meta, s varEvaler) *cfgSplice { - return &cfgSplice{cfgPrimitive{ctx, m}, s} + id := string(atomic.AddInt32(&spliceSeq, 1)) + uuid.NewV4().String() + return &cfgSplice{cfgPrimitive{ctx, m}, id, s} } func (p *cfgPrimitive) Context() context { return p.ctx } @@ -454,8 +470,12 @@ func (r *cfgRef) resolve(opts *options) (value, error) { return r.ref.resolve(r.ctx.getParent(), opts) } -func (*cfgSplice) typ(*options) (typeInfo, error) { - return typeInfo{"string", tString}, nil +func (s *cfgSplice) typ(opt *options) (typeInfo, error) { + v, err := s.getValue(opt) + if err != nil { + return typeInfo{}, err + } + return v.typ(opt) } func (s *cfgSplice) cpy(ctx context) value { @@ -463,51 +483,120 @@ func (s *cfgSplice) cpy(ctx context) value { } func (s *cfgSplice) reflect(opt *options) (reflect.Value, error) { - str, err := s.toString(opt) + v, err := s.getValue(opt) if err != nil { return reflect.Value{}, err } - return reflect.ValueOf(str), err + return v.reflect(opt) } func (s *cfgSplice) reify(opt *options) (interface{}, error) { - return s.toString(opt) + v, err := s.getValue(opt) + if err != nil { + return false, err + } + return v.reify(opt) } func (s *cfgSplice) toBool(opt *options) (bool, error) { - str, err := s.toString(opt) + v, err := s.getValue(opt) if err != nil { return false, err } - return strconv.ParseBool(str) + return v.toBool(opt) } func (s *cfgSplice) toString(opts *options) (string, error) { - return s.splice.eval(s.ctx.getParent(), opts) + v, err := s.getValue(opts) + if err != nil { + return "", err + } + return v.toString(opts) } func (s *cfgSplice) toInt(opt *options) (int64, error) { - str, err := s.toString(opt) + v, err := s.getValue(opt) if err != nil { return 0, err } - return strconv.ParseInt(str, 0, 64) + return v.toInt(opt) } func (s *cfgSplice) toUint(opt *options) (uint64, error) { - str, err := s.toString(opt) + v, err := s.getValue(opt) if err != nil { return 0, err } - return strconv.ParseUint(str, 0, 64) + return v.toUint(opt) } func (s *cfgSplice) toFloat(opt *options) (float64, error) { - str, err := s.toString(opt) + v, err := s.getValue(opt) if err != nil { return 0, err } - return strconv.ParseFloat(str, 64) + return v.toFloat(opt) +} + +func (s *cfgSplice) toConfig(opt *options) (*Config, error) { + v, err := s.getValue(opt) + if err != nil { + return nil, err + } + return v.toConfig(opt) +} + +func (s *cfgSplice) getValue(opts *options) (value, error) { + if v, ok := opts.parsed[s.id]; ok { + if v.err != nil { + return nil, v.err + } + return v.value, nil + } + + v, err := s.parseValue(opts) + opts.parsed[s.id] = spliceValue{err, v} + return v, err +} + +func (s *cfgSplice) parseValue(opts *options) (value, error) { + str, err := s.splice.eval(s.ctx.getParent(), opts) + if err != nil { + return nil, err + } + + ifc, err := parse.ParseValue(str) + if err != nil { + return nil, err + } + + if ifc == nil { + if strings.TrimSpace(str) == "" { + return newString(s.ctx, s.meta(), str), nil + } + return &cfgNil{cfgPrimitive{ctx: s.ctx, metadata: s.meta()}}, nil + } + + switch v := ifc.(type) { + case bool: + return newBool(s.ctx, s.meta(), v), nil + case int64: + return newInt(s.ctx, s.meta(), v), nil + case uint64: + return newUint(s.ctx, s.meta(), v), nil + case float64: + return newFloat(s.ctx, s.meta(), v), nil + case string: + return newString(s.ctx, s.meta(), v), nil + } + + sub, err := normalize(opts, ifc) + if err != nil { + return nil, err + } + sub.ctx = s.ctx + sub.metadata = s.metadata + return cfgSub{sub}, nil } func isNil(v value) bool { diff --git a/vendor/github.com/elastic/go-ucfg/ucfg.go b/vendor/github.com/elastic/go-ucfg/ucfg.go index 9972f20f91b..e959d5c0fb5 100644 --- a/vendor/github.com/elastic/go-ucfg/ucfg.go +++ b/vendor/github.com/elastic/go-ucfg/ucfg.go @@ -7,6 +7,15 @@ import ( "time" ) +// Config object to store hierarchical configurations into. Config can be +// both a dictionary and a list holding primitive values. Primitive values +// can be booleans, integers, float point numbers and strings. +// +// Config provides a low level interface for setting and getting settings +// via SetBool, SetInt, SetUing, SetFloat, SetString, SetChild, Bool, Int, Uint, +// Float, String, and Child. +// +// A more user-friendly high level interface is provided via Unpack and Merge. type Config struct { ctx context metadata *Meta @@ -24,15 +33,11 @@ type fields struct { a []value } -// Meta holds additional meta data per config value +// Meta holds additional meta data per config value. type Meta struct { Source string } -type Unpacker interface { - Unpack(interface{}) error -} - var ( tConfig = reflect.TypeOf(Config{}) tConfigPtr = reflect.PtrTo(tConfig) @@ -40,7 +45,6 @@ var ( tInterfaceArray = reflect.TypeOf([]interface{}(nil)) // interface types - tUnpacker = reflect.TypeOf((*Unpacker)(nil)).Elem() tValidator = reflect.TypeOf((*Validator)(nil)).Elem() // primitives @@ -53,12 +57,17 @@ var ( tRegexp = reflect.TypeOf(regexp.Regexp{}) ) +// New creates a new empty Config object. func New() *Config { return &Config{ fields: &fields{nil, nil}, } } +// NewFrom creates a new config object normalizing and copying from into the new +// Config object. NewFrom uses Merge to copy from. +// +// NewFrom supports the options: PathSep, MetaData, StructTag, VarExp func NewFrom(from interface{}, opts ...Option) (*Config, error) { c := New() if err := c.Merge(from, opts...); err != nil { @@ -67,14 +76,17 @@ func NewFrom(from interface{}, opts ...Option) (*Config, error) { return c, nil } +// IsDict checks if c has named keys. func (c *Config) IsDict() bool { return c.fields.dict() != nil } +// IsArray checks if c has index only accessible settings. func (c *Config) IsArray() bool { return c.fields.array() != nil } +// GetFields returns a list of all top-level named keys in c. func (c *Config) GetFields() []string { var names []string for k := range c.fields.dict() { @@ -83,19 +95,26 @@ func (c *Config) GetFields() []string { return names } +// HasField checks if c has a top-level named key name. func (c *Config) HasField(name string) bool { _, ok := c.fields.get(name) return ok } +// Path gets the absolute path of c separated by sep. If c is a root-Config an +// empty string will be returned. func (c *Config) Path(sep string) string { return c.ctx.path(sep) } +// PathOf gets the absolute path of a potential setting field in c with name +// separated by sep. func (c *Config) PathOf(field, sep string) string { return c.ctx.pathOf(field, sep) } +// Parent returns the parent configuration or nil if c is already a root +// Configuration. func (c *Config) Parent() *Config { ctx := c.ctx for { diff --git a/vendor/github.com/elastic/go-ucfg/unpack.go b/vendor/github.com/elastic/go-ucfg/unpack.go new file mode 100644 index 00000000000..da859700cba --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/unpack.go @@ -0,0 +1,165 @@ +package ucfg + +import "reflect" + +// Unpacker type used by Unpack to allow types to implement custom configuration +// unpacking. +type Unpacker interface { + // Unpack is called if a setting of field has a type implementing Unpacker. + // + // The interface{} value passed to Unpack can be of type: bool, int64, uint64, + // float64, string, []interface{} or map[string]interface{}. + Unpack(interface{}) error +} + +// BoolUnpacker interface specializes the Unpacker interface +// by casting values to bool when calling Unpack. +type BoolUnpacker interface { + Unpack(b bool) error +} + +// IntUnpacker interface specializes the Unpacker interface +// by casting values to int64 when calling Unpack. +type IntUnpacker interface { + Unpack(i int64) error +} + +// UintUnpacker interface specializes the Unpacker interface +// by casting values to uint64 when calling Unpack. +type UintUnpacker interface { + Unpack(u uint64) error +} + +// FloatUnpacker interface specializes the Unpacker interface +// by casting values to float64 when calling Unpack. +type FloatUnpacker interface { + Unpack(f float64) error +} + +// StringUnpacker interface specializes the Unpacker interface +// by casting values to string when calling Unpack. +type StringUnpacker interface { + Unpack(s string) error +} + +// ConfigUnpacker interface specializes the Unpacker interface +// by passing the the *Config object directly instead of +// transforming the *Config object into map[string]interface{}. +type ConfigUnpacker interface { + Unpack(c *Config) error +} + +var ( + // unpacker interface types + tUnpacker = reflect.TypeOf((*Unpacker)(nil)).Elem() + tBoolUnpacker = reflect.TypeOf((*BoolUnpacker)(nil)).Elem() + tIntUnpacker = reflect.TypeOf((*IntUnpacker)(nil)).Elem() + tUintUnpacker = reflect.TypeOf((*UintUnpacker)(nil)).Elem() + tFloatUnpacker = reflect.TypeOf((*FloatUnpacker)(nil)).Elem() + tStringUnpacker = reflect.TypeOf((*StringUnpacker)(nil)).Elem() + tConfigUnpacker = reflect.TypeOf((*ConfigUnpacker)(nil)).Elem() + + tUnpackers = [...]reflect.Type{ + tUnpacker, + tBoolUnpacker, + tIntUnpacker, + tUintUnpacker, + tFloatUnpacker, + tStringUnpacker, + tConfigUnpacker, + } +) + +// valueIsUnpacker checks if v implements the Unpacker interface. +// If there exists a pointer to v, the pointer to v is also tested. +func valueIsUnpacker(v reflect.Value) (reflect.Value, bool) { + for { + if implementsUnpacker(v.Type()) { + return v, true + } + + if !v.CanAddr() { + break + } + v = v.Addr() + } + + return reflect.Value{}, false +} + +func typeIsUnpacker(t reflect.Type) (reflect.Value, bool) { + if implementsUnpacker(t) { + return reflect.New(t).Elem(), true + } + + if implementsUnpacker(reflect.PtrTo(t)) { + return reflect.New(t), true + } + + return reflect.Value{}, false +} + +func implementsUnpacker(t reflect.Type) bool { + for _, tUnpack := range tUnpackers { + if t.Implements(tUnpack) { + return true + } + } + + return false +} + +func unpackWith(opts *options, v reflect.Value, with value) Error { + ctx := with.Context() + meta := with.meta() + + var err error + switch u := v.Interface().(type) { + case Unpacker: + var reified interface{} + if reified, err = with.reify(opts); err == nil { + err = u.Unpack(reified) + } + + case BoolUnpacker: + var b bool + if b, err = with.toBool(opts); err == nil { + err = u.Unpack(b) + } + + case IntUnpacker: + var n int64 + if n, err = with.toInt(opts); err == nil { + err = u.Unpack(n) + } + + case UintUnpacker: + var n uint64 + if n, err = with.toUint(opts); err == nil { + err = u.Unpack(n) + } + + case FloatUnpacker: + var f float64 + if f, err = with.toFloat(opts); err == nil { + err = u.Unpack(f) + } + + case StringUnpacker: + var s string + if s, err = with.toString(opts); err == nil { + err = u.Unpack(s) + } + + case ConfigUnpacker: + var c *Config + if c, err = with.toConfig(opts); err == nil { + err = u.Unpack(c) + } + } + + if err != nil { + return raisePathErr(err, meta, "", ctx.path(".")) + } + return nil +} diff --git a/vendor/github.com/elastic/go-ucfg/util.go b/vendor/github.com/elastic/go-ucfg/util.go index cb80d83d78b..664317ecb7a 100644 --- a/vendor/github.com/elastic/go-ucfg/util.go +++ b/vendor/github.com/elastic/go-ucfg/util.go @@ -131,37 +131,3 @@ func isFloat(k reflect.Kind) bool { return false } } - -func implementsUnpacker(v reflect.Value) (reflect.Value, bool) { - for { - if v.Type().Implements(tUnpacker) { - return v, true - } - - if !v.CanAddr() { - break - } - v = v.Addr() - } - return reflect.Value{}, false -} - -func typeIsUnpacker(t reflect.Type) (reflect.Value, bool) { - if t.Implements(tUnpacker) { - return reflect.New(t).Elem(), true - } - - if reflect.PtrTo(t).Implements(tUnpacker) { - return reflect.New(t), true - } - - return reflect.Value{}, false -} - -func unpackWith(ctx context, meta *Meta, v reflect.Value, with interface{}) Error { - err := v.Interface().(Unpacker).Unpack(with) - if err != nil { - return raisePathErr(err, meta, "", ctx.path(".")) - } - return nil -} diff --git a/vendor/github.com/elastic/go-ucfg/validator.go b/vendor/github.com/elastic/go-ucfg/validator.go index d1adb54ea1b..e8bb96cb811 100644 --- a/vendor/github.com/elastic/go-ucfg/validator.go +++ b/vendor/github.com/elastic/go-ucfg/validator.go @@ -9,10 +9,19 @@ import ( "time" ) +// Validator interface provides additional validation support to Unpack. The +// Validate method will be executed for any type passed directly or indirectly to +// Unpack. +// +// If Validate fails with an error message, Unpack will add some +// context - like setting being accessed and file setting was read from - to the +// error message before returning the actual error. type Validator interface { Validate() error } +// ValidatorCallback is the type of optional validator tags to be registered via +// RegisterValidator. type ValidatorCallback func(interface{}, string) error type validatorTag struct { @@ -26,13 +35,21 @@ var ( ) func init() { - RegisterValidator("nonzero", validateNonZero) - RegisterValidator("positive", validatePositive) - RegisterValidator("min", validateMin) - RegisterValidator("max", validateMax) - RegisterValidator("required", validateRequired) + initRegisterValidator("nonzero", validateNonZero) + initRegisterValidator("positive", validatePositive) + initRegisterValidator("min", validateMin) + initRegisterValidator("max", validateMax) + initRegisterValidator("required", validateRequired) } +func initRegisterValidator(name string, cb ValidatorCallback) { + if err := RegisterValidator(name, cb); err != nil { + panic("Duplicate validator: " + name) + } +} + +// RegisterValidator adds a new validator option to the "validate" struct tag. +// The callback will be executed when unpacking into a struct field. func RegisterValidator(name string, cb ValidatorCallback) error { if _, exists := validators[name]; exists { return ErrDuplicateValidator diff --git a/vendor/github.com/elastic/go-ucfg/variables.go b/vendor/github.com/elastic/go-ucfg/variables.go index b82d0d77e7a..ad5e3e83790 100644 --- a/vendor/github.com/elastic/go-ucfg/variables.go +++ b/vendor/github.com/elastic/go-ucfg/variables.go @@ -390,9 +390,7 @@ func lexer(in string) (<-chan token, <-chan error) { } func parseVarExp(lex <-chan token, pathSep string) (varEvaler, error) { - stack := []parseState{ - parseState{st: stLeft}, - } + stack := []parseState{{st: stLeft}} // parser loop for tok := range lex {