diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index af2235ff4b2..b73ceba676c 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -21,7 +21,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Refactor metadata generator to support adding metadata across resources {pull}14875[14875] - Update to ECS 1.4.0. {pull}14844[14844] - The document id fields has been renamed from @metadata.id to @metadata._id {pull}15859[15859] - +- Variable substitution from environment variables is not longer supported. {pull}15937{15937} *Auditbeat* @@ -74,6 +74,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix issue where TLS settings would be ignored when a forward proxy was in use. {pull}15516{15516} - Remove superfluous use of number_of_routing_shards setting from the default template. {pull}16038[16038] - Fix index names for indexing not always guaranteed to be lower case. {pull}16081[16081] +- Upgrade go-ucfg to latest v0.8.1. {pull}15937{15937} *Auditbeat* diff --git a/NOTICE.txt b/NOTICE.txt index bfe230e4640..7d835858145 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1518,8 +1518,8 @@ Apache License 2.0 -------------------------------------------------------------------- Dependency: github.com/elastic/go-ucfg -Version: v0.7.0 -Revision: 0539807037ce820e147797f051ff32b05f4f9288 +Version: v0.8.1 +Revision: 093a6898c440d2e5e93abf09850068c60428b2cd License type (autodetected): Apache-2.0 ./vendor/github.com/elastic/go-ucfg/LICENSE: -------------------------------------------------------------------- @@ -6611,6 +6611,34 @@ License type (autodetected): Apache-2.0 Apache License 2.0 +-------------------------------------------------------------------- +Dependency: gopkg.in/hjson/hjson-go.v3 +Revision: 8f44e1182a33737b57cb6855fe656ece998e9b3c +License type (autodetected): MIT +./vendor/gopkg.in/hjson/hjson-go.v3/LICENSE: +-------------------------------------------------------------------- +MIT License + +Copyright (c) 2016, 2017 Christian Zangl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + -------------------------------------------------------------------- Dependency: gopkg.in/inf.v0 Revision: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 diff --git a/filebeat/fileset/modules_test.go b/filebeat/fileset/modules_test.go index 76e091aee79..42f40e2f0b0 100644 --- a/filebeat/fileset/modules_test.go +++ b/filebeat/fileset/modules_test.go @@ -162,6 +162,7 @@ func TestApplyOverrides(t *testing.T) { "a": "test", "b.c": "test", }, + Input: map[string]interface{}{}, }, module: "nginx", fileset: "access", @@ -177,6 +178,7 @@ func TestApplyOverrides(t *testing.T) { "a": "test1", "b": map[string]interface{}{"c": "test2"}, }, + Input: map[string]interface{}{}, }, }, { @@ -186,6 +188,7 @@ func TestApplyOverrides(t *testing.T) { Var: map[string]interface{}{ "paths": []string{"/var/log/nginx"}, }, + Input: map[string]interface{}{}, }, module: "nginx", fileset: "access", @@ -201,6 +204,7 @@ func TestApplyOverrides(t *testing.T) { Var: map[string]interface{}{ "paths": []interface{}{"/var/local/nginx/log"}, }, + Input: map[string]interface{}{}, }, }, { @@ -219,14 +223,17 @@ func TestApplyOverrides(t *testing.T) { Input: map[string]interface{}{ "close_eof": true, }, + Var: map[string]interface{}{}, }, }, } for _, test := range tests { - result, err := applyOverrides(&test.fcfg, test.module, test.fileset, test.overrides) - assert.NoError(t, err) - assert.Equal(t, &test.expected, result, test.name) + t.Run(test.name, func(t *testing.T) { + result, err := applyOverrides(&test.fcfg, test.module, test.fileset, test.overrides) + assert.NoError(t, err) + assert.Equal(t, &test.expected, result) + }) } } @@ -314,9 +321,11 @@ func TestAppendWithoutDuplicates(t *testing.T) { } for _, test := range tests { - result, err := appendWithoutDuplicates(test.configs, test.modules) - assert.NoError(t, err, test.name) - assert.Equal(t, test.expected, result, test.name) + t.Run(test.name, func(t *testing.T) { + result, err := appendWithoutDuplicates(test.configs, test.modules) + assert.NoError(t, err) + assert.Equal(t, test.expected, result) + }) } } @@ -338,6 +347,8 @@ func TestMcfgFromConfig(t *testing.T) { Filesets: map[string]*FilesetConfig{ "error": { Enabled: &falseVar, + Var: map[string]interface{}{}, + Input: map[string]interface{}{}, }, }, }, @@ -355,6 +366,7 @@ func TestMcfgFromConfig(t *testing.T) { Var: map[string]interface{}{ "test": false, }, + Input: map[string]interface{}{}, }, }, }, @@ -362,13 +374,15 @@ func TestMcfgFromConfig(t *testing.T) { } for _, test := range tests { - result, err := mcfgFromConfig(test.config) - assert.NoError(t, err, test.name) - assert.Equal(t, test.expected.Module, result.Module) - assert.Equal(t, len(test.expected.Filesets), len(result.Filesets)) - for name, fileset := range test.expected.Filesets { - assert.Equal(t, fileset, result.Filesets[name]) - } + t.Run(test.name, func(t *testing.T) { + result, err := mcfgFromConfig(test.config) + assert.NoError(t, err) + assert.Equal(t, test.expected.Module, result.Module) + assert.Equal(t, len(test.expected.Filesets), len(result.Filesets)) + for name, fileset := range test.expected.Filesets { + assert.Equal(t, fileset, result.Filesets[name]) + } + }) } } @@ -428,7 +442,9 @@ func TestInterpretError(t *testing.T) { } for _, test := range tests { - errResult := interpretError(errors.New("test"), []byte(test.Input)) - assert.Equal(t, errResult.Error(), test.Output, test.Test) + t.Run(test.Test, func(t *testing.T) { + errResult := interpretError(errors.New("test"), []byte(test.Input)) + assert.Equal(t, errResult.Error(), test.Output, test.Test) + }) } } diff --git a/filebeat/inputsource/udp/config.go b/filebeat/inputsource/udp/config.go index b64b48a9056..f269a805cb5 100644 --- a/filebeat/inputsource/udp/config.go +++ b/filebeat/inputsource/udp/config.go @@ -28,5 +28,5 @@ type Config struct { Host string `config:"host"` MaxMessageSize cfgtype.ByteSize `config:"max_message_size" validate:"positive,nonzero"` Timeout time.Duration `config:"timeout"` - ReadBuffer cfgtype.ByteSize `config:"read_buffer" validate:"positive,nonzero"` + ReadBuffer cfgtype.ByteSize `config:"read_buffer" validate:"positive"` } diff --git a/filebeat/tests/system/test_reload_modules.py b/filebeat/tests/system/test_reload_modules.py index 3c97ed0b273..b22294e7d9a 100644 --- a/filebeat/tests/system/test_reload_modules.py +++ b/filebeat/tests/system/test_reload_modules.py @@ -103,7 +103,7 @@ def test_no_es_connection(self): reload_path=self.working_dir + "/configs/*.yml", reload_type="modules", inputs=False, - elasticsearch={"host": 'errorhost:9201'} + elasticsearch={"host": 'errorhost:9201', "timeout": '1'} ) proc = self.start_beat() diff --git a/filebeat/tests/system/test_shutdown.py b/filebeat/tests/system/test_shutdown.py index 7d31af558e0..4ab6cc670a5 100644 --- a/filebeat/tests/system/test_shutdown.py +++ b/filebeat/tests/system/test_shutdown.py @@ -81,8 +81,9 @@ def test_shutdown_wait_timeout(self): self.nasa_logs() + # Use 'localhost' so connection is refused instantly self.render_config_template( - logstash={"host": "does.not.exist:12345"}, + logstash={"host": "localhost:12345", "timeout": 1}, path=os.path.abspath(self.working_dir) + "/log/*", ignore_older="1h", shutdown_timeout="1s", diff --git a/journalbeat/input/input_test.go b/journalbeat/input/input_test.go index 3cbc416133e..76e99852c9a 100644 --- a/journalbeat/input/input_test.go +++ b/journalbeat/input/input_test.go @@ -59,44 +59,46 @@ func TestProcessorsForInput(t *testing.T) { }, } for description, test := range testCases { - if test.event.Fields == nil { - test.event.Fields = common.MapStr{} - } - config, err := inputConfigFromString(test.configStr) - if err != nil { - t.Errorf("[%s] %v", description, err) - continue - } - processors, err := processorsForInput(test.beatInfo, config) - if err != nil { - t.Errorf("[%s] %v", description, err) - continue - } - processedEvent, err := processors.Run(&test.event) - // We don't check if err != nil, because we are testing the final outcome - // of running the processors, including when some of them fail. - if processedEvent == nil { - t.Errorf("[%s] Unexpected fatal error running processors: %v\n", - description, err) - } - for key, value := range test.expectedFields { - field, err := processedEvent.GetValue(key) + t.Run(description, func(t *testing.T) { + if test.event.Fields == nil { + test.event.Fields = common.MapStr{} + } + config, err := inputConfigFromString(test.configStr) + if err != nil { + t.Errorf("[%s] %v", description, err) + return + } + processors, err := processorsForInput(test.beatInfo, config) if err != nil { - t.Errorf("[%s] Couldn't get field %s from event: %v", description, key, err) - continue + t.Errorf("[%s] %v", description, err) + return } - assert.Equal(t, field, value) - fieldStr, ok := field.(string) - if !ok { - // Note that requiring a string here is just to simplify the test setup, - // not a requirement of the underlying api. - t.Errorf("[%s] Field [%s] should be a string", description, key) - continue + processedEvent, err := processors.Run(&test.event) + // We don't check if err != nil, because we are testing the final outcome + // of running the processors, including when some of them fail. + if processedEvent == nil { + t.Errorf("[%s] Unexpected fatal error running processors: %v\n", + description, err) } - if fieldStr != value { - t.Errorf("[%s] Event field [%s]: expected [%s], got [%s]", description, key, value, fieldStr) + for key, value := range test.expectedFields { + field, err := processedEvent.GetValue(key) + if err != nil { + t.Errorf("[%s] Couldn't get field %s from event: %v", description, key, err) + return + } + assert.Equal(t, field, value) + fieldStr, ok := field.(string) + if !ok { + // Note that requiring a string here is just to simplify the test setup, + // not a requirement of the underlying api. + t.Errorf("[%s] Field [%s] should be a string", description, key) + return + } + if fieldStr != value { + t.Errorf("[%s] Event field [%s]: expected [%s], got [%s]", description, key, value, fieldStr) + } } - } + }) } } @@ -147,7 +149,7 @@ func (p *setRawIndex) String() string { // Helper function to convert from YML input string to an unpacked // Config func inputConfigFromString(s string) (Config, error) { - config := Config{} + config := DefaultConfig cfg, err := common.NewConfigFrom(s) if err != nil { return config, err diff --git a/libbeat/common/flags_test.go b/libbeat/common/flags_test.go index f7e545dbcb6..7793ef90ee7 100644 --- a/libbeat/common/flags_test.go +++ b/libbeat/common/flags_test.go @@ -91,7 +91,7 @@ func TestSettingsFlag(t *testing.T) { in []string expected map[string]interface{} }{ - {nil, nil}, + {nil, map[string]interface{}{}}, {[]string{"a=1"}, map[string]interface{}{"a": uint64(1)}}, {[]string{"a=1", "b=false"}, map[string]interface{}{"a": uint64(1), "b": false}}, {[]string{"a=1", "b"}, map[string]interface{}{"a": uint64(1), "b": true}}, diff --git a/libbeat/conditions/conditions.go b/libbeat/conditions/conditions.go index 586e185df51..9ddd39ac4fe 100644 --- a/libbeat/conditions/conditions.go +++ b/libbeat/conditions/conditions.go @@ -72,7 +72,7 @@ func NewCondition(config *Config) (Condition, error) { condition, err = NewRangeCondition(config.Range.fields) case config.HasFields != nil: condition = NewHasFieldsCondition(config.HasFields) - case config.Network != nil: + case config.Network != nil && len(config.Network) > 0: condition, err = NewNetworkCondition(config.Network) case len(config.OR) > 0: var conditionsList []Condition diff --git a/libbeat/idxmgmt/std_test.go b/libbeat/idxmgmt/std_test.go index c2985f262e6..b3ba0b123ed 100644 --- a/libbeat/idxmgmt/std_test.go +++ b/libbeat/idxmgmt/std_test.go @@ -288,7 +288,7 @@ func TestIndexManager_Setup(t *testing.T) { if c.Settings.Index != nil { c.Settings.Index = (map[string]interface{})(common.MapStr(c.Settings.Index).Clone()) } - if c.Settings.Index != nil { + if c.Settings.Source != nil { c.Settings.Source = (map[string]interface{})(common.MapStr(c.Settings.Source).Clone()) } return c @@ -302,6 +302,12 @@ func TestIndexManager_Setup(t *testing.T) { if err != nil { panic(err) } + if s.Settings.Index != nil && len(s.Settings.Index) == 0 { + s.Settings.Index = nil + } + if s.Settings.Source != nil && len(s.Settings.Source) == 0 { + s.Settings.Source = nil + } } return &s } diff --git a/libbeat/keystore/keystore.go b/libbeat/keystore/keystore.go index 98ff30249af..15937b5163e 100644 --- a/libbeat/keystore/keystore.go +++ b/libbeat/keystore/keystore.go @@ -23,6 +23,7 @@ import ( "github.com/elastic/beats/libbeat/common" ucfg "github.com/elastic/go-ucfg" + "github.com/elastic/go-ucfg/parse" ) var ( @@ -91,7 +92,7 @@ func Factory(cfg *common.Config, defaultPath string) (Keystore, error) { } // ResolverFromConfig create a resolver from a configuration. -func ResolverFromConfig(cfg *common.Config, dataPath string) (func(string) (string, error), error) { +func ResolverFromConfig(cfg *common.Config, dataPath string) (func(string) (string, parse.Config, error), error) { keystore, err := Factory(cfg, dataPath) if err != nil { @@ -102,24 +103,24 @@ func ResolverFromConfig(cfg *common.Config, dataPath string) (func(string) (stri } // ResolverWrap wrap a config resolver around an existing keystore. -func ResolverWrap(keystore Keystore) func(string) (string, error) { - return func(keyName string) (string, error) { +func ResolverWrap(keystore Keystore) func(string) (string, parse.Config, error) { + return func(keyName string) (string, parse.Config, error) { key, err := keystore.Retrieve(keyName) if err != nil { // If we cannot find the key, its a non fatal error // and we pass to other resolver. if err == ErrKeyDoesntExists { - return "", ucfg.ErrMissing + return "", parse.DefaultConfig, ucfg.ErrMissing } - return "", err + return "", parse.DefaultConfig, err } v, err := key.Get() if err != nil { - return "", err + return "", parse.DefaultConfig, err } - return string(v), nil + return string(v), parse.DefaultConfig, nil } } diff --git a/libbeat/keystore/keystore_test.go b/libbeat/keystore/keystore_test.go index 31ce6624425..36c96f3fe96 100644 --- a/libbeat/keystore/keystore_test.go +++ b/libbeat/keystore/keystore_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" ucfg "github.com/elastic/go-ucfg" + "github.com/elastic/go-ucfg/parse" ) func TestResolverWhenTheKeyDoesntExist(t *testing.T) { @@ -33,7 +34,7 @@ func TestResolverWhenTheKeyDoesntExist(t *testing.T) { keystore := CreateAnExistingKeystore(path) resolver := ResolverWrap(keystore) - _, err := resolver("donotexist") + _, _, err := resolver("donotexist") assert.Equal(t, err, ucfg.ErrMissing) } @@ -44,7 +45,8 @@ func TestResolverWhenTheKeyExist(t *testing.T) { keystore := CreateAnExistingKeystore(path) resolver := ResolverWrap(keystore) - v, err := resolver("output.elasticsearch.password") + v, pCfg, err := resolver("output.elasticsearch.password") assert.NoError(t, err) + assert.Equal(t, pCfg, parse.DefaultConfig) assert.Equal(t, v, "secret") } diff --git a/libbeat/tests/system/config/libbeat.yml.j2 b/libbeat/tests/system/config/libbeat.yml.j2 index 1a61a2258b1..edfc178eea8 100644 --- a/libbeat/tests/system/config/libbeat.yml.j2 +++ b/libbeat/tests/system/config/libbeat.yml.j2 @@ -83,11 +83,17 @@ output: {% if elasticsearch.ilm %} ilm.enabled: {{elasticsearch.ilm}} {% endif %} + {% if elasticsearch.timeout %} + timeout: {{elasticsearch.timeout}} + {% endif %} {%- endif %} {% if logstash %} output.logstash: hosts: ["{{ logstash.host }}"] + {% if logstash.timeout %} + timeout: {{logstash.timeout}} + {% endif %} {%- endif %} {% if not (console or elasticsearch or logstash) -%} diff --git a/metricbeat/mb/lightmodules_test.go b/metricbeat/mb/lightmodules_test.go index 35909923e74..90f81242651 100644 --- a/metricbeat/mb/lightmodules_test.go +++ b/metricbeat/mb/lightmodules_test.go @@ -195,18 +195,22 @@ func TestNewModuleFromConfig(t *testing.T) { "normal module": { config: common.MapStr{"module": "foo", "metricsets": []string{"bar"}}, expectedOption: "default", + expectedQuery: QueryParams{}, }, "light module": { config: common.MapStr{"module": "service", "metricsets": []string{"metricset"}}, expectedOption: "test", + expectedQuery: QueryParams{}, }, "light module default metricset": { config: common.MapStr{"module": "service"}, expectedOption: "test", + expectedQuery: QueryParams{}, }, "light module override option": { config: common.MapStr{"module": "service", "option": "overriden"}, expectedOption: "overriden", + expectedQuery: QueryParams{}, }, "light module with query": { config: common.MapStr{"module": "service", "query": common.MapStr{"param": "foo"}}, @@ -217,6 +221,7 @@ func TestNewModuleFromConfig(t *testing.T) { config: common.MapStr{"module": "service", "period": "42s"}, expectedOption: "test", expectedPeriod: 42 * time.Second, + expectedQuery: QueryParams{}, }, "light module is broken": { config: common.MapStr{"module": "broken"}, @@ -233,6 +238,7 @@ func TestNewModuleFromConfig(t *testing.T) { "mixed module with standard and light metricsets": { config: common.MapStr{"module": "mixed", "metricsets": []string{"standard", "light"}}, expectedOption: "default", + expectedQuery: QueryParams{}, }, "mixed module with unregistered and light metricsets": { config: common.MapStr{"module": "mixedbroken", "metricsets": []string{"unregistered", "light"}}, diff --git a/metricbeat/mb/mb_test.go b/metricbeat/mb/mb_test.go index 3e3b21ea30a..3094e1588d6 100644 --- a/metricbeat/mb/mb_test.go +++ b/metricbeat/mb/mb_test.go @@ -75,15 +75,18 @@ func (m *testPushMetricSet) Run(r PushReporter) {} func TestModuleConfig(t *testing.T) { tests := []struct { - in interface{} - out ModuleConfig - err string + name string + in interface{} + out ModuleConfig + err string }{ { - in: map[string]interface{}{}, - err: "missing required field accessing 'module'", + name: "missing required field", + in: map[string]interface{}{}, + err: "missing required field accessing 'module'", }, { + name: "valid config", in: map[string]interface{}{ "module": "example", "metricsets": []string{"test"}, @@ -94,9 +97,11 @@ func TestModuleConfig(t *testing.T) { Enabled: true, Period: time.Second * 10, Timeout: 0, + Query: QueryParams{}, }, }, { + name: "missing period", in: map[string]interface{}{ "module": "example", "metricsets": []string{"test"}, @@ -105,6 +110,7 @@ func TestModuleConfig(t *testing.T) { err: "negative value accessing 'period'", }, { + name: "negative timeout", in: map[string]interface{}{ "module": "example", "metricsets": []string{"test"}, @@ -115,27 +121,29 @@ func TestModuleConfig(t *testing.T) { } for i, test := range tests { - c, err := common.NewConfigFrom(test.in) - if err != nil { - t.Fatal(err) - } - - unpackedConfig := DefaultModuleConfig() - err = c.Unpack(&unpackedConfig) - if err != nil && test.err == "" { - t.Errorf("unexpected error while unpacking in testcase %d: %v", i, err) - continue - } - if test.err != "" { + t.Run(test.name, func(t *testing.T) { + c, err := common.NewConfigFrom(test.in) if err != nil { - assert.Contains(t, err.Error(), test.err, "testcase %d", i) - } else { - t.Errorf("expected error '%v' in testcase %d", test.err, i) + t.Fatal(err) } - continue - } - assert.Equal(t, test.out, unpackedConfig) + unpackedConfig := DefaultModuleConfig() + err = c.Unpack(&unpackedConfig) + if err != nil && test.err == "" { + t.Errorf("unexpected error while unpacking in testcase %d: %v", i, err) + return + } + if test.err != "" { + if err != nil { + assert.Contains(t, err.Error(), test.err, "testcase %d", i) + } else { + t.Errorf("expected error '%v' in testcase %d", test.err, i) + } + return + } + + assert.Equal(t, test.out, unpackedConfig) + }) } } diff --git a/metricbeat/module/mysql/mysql.go b/metricbeat/module/mysql/mysql.go index 96d844658f9..5497f92cc93 100644 --- a/metricbeat/module/mysql/mysql.go +++ b/metricbeat/module/mysql/mysql.go @@ -39,7 +39,7 @@ func init() { func NewModule(base mb.BaseModule) (mb.Module, error) { // Validate that at least one host has been specified. config := struct { - Hosts []string `config:"hosts" validate:"nonzero,required"` + Hosts []string `config:"hosts" validate:"required"` }{} if err := base.UnpackConfig(&config); err != nil { return nil, err diff --git a/vendor/github.com/elastic/go-ucfg/CHANGELOG.md b/vendor/github.com/elastic/go-ucfg/CHANGELOG.md index f6c0b4c3026..4349863cb7b 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.8.1] + +### Fixed +- Prevent Validate from being called when value is a pointer or interface and is nil. #144 + +## [0.8.0] + +### Added +- Add support for HJSON. #131 +- Add new parse.Config to adjust parsing of varibles returned by a Resolve. #139 +- Add call to InitDefaults when map, primitives, or structs implement Initializer interface during Unpack. #104 + +### Changed +- Moved internal/parse to parse module. #139 +- Add parse.Config to resolvers return. #139 + +### Fixed +- Call Validate on custom slice types. #133 +- Call Validate on custom map types. #136 +- Disabled object parsing of environment variables. #139 +- Apply validation to defaults passed into Unpack when Config doesn't contain a value. #42 + ## [0.7.0] ### Added @@ -234,7 +256,9 @@ 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.7.0...HEAD +[Unreleased]: https://github.com/elastic/go-ucfg/compare/v0.8.1...HEAD +[0.8.1]: https://github.com/elastic/go-ucfg/compare/v0.8.0...v0.8.1 +[0.8.0]: https://github.com/elastic/go-ucfg/compare/v0.7.0...v0.8.0 [0.7.0]: https://github.com/elastic/go-ucfg/compare/v0.6.5...v0.7.0 [0.6.5]: https://github.com/elastic/go-ucfg/compare/v0.6.4...v0.6.5 [0.6.4]: https://github.com/elastic/go-ucfg/compare/v0.6.3...v0.6.4 diff --git a/vendor/github.com/elastic/go-ucfg/README.md b/vendor/github.com/elastic/go-ucfg/README.md index 9898e38a46f..1042f426339 100644 --- a/vendor/github.com/elastic/go-ucfg/README.md +++ b/vendor/github.com/elastic/go-ucfg/README.md @@ -2,11 +2,12 @@ Status](https://travis-ci.org/elastic/go-ucfg.svg?branch=master)](https://travis-ci.org/elastic/go-ucfg) [![Go Report Card](https://goreportcard.com/badge/github.com/elastic/go-ucfg)](https://goreportcard.com/report/github.com/elastic/go-ucfg) +[![codecov](https://codecov.io/gh/elastic/go-ucfg/branch/master/graph/badge.svg)](https://codecov.io/gh/elastic/go-ucfg) # ucfg - Universal Configuration -`ucfg` is a Golang library to handle yaml and json configuration files in your Golang project. It was developed for the [libbeat framework](https://github.com/elastic/beats/tree/master/libbeat) and used by all [beats](https://github.com/elastic/beats). +`ucfg` is a Golang library to handle hjson, json, and yaml configuration files in your Golang project. It was developed for the [libbeat framework](https://github.com/elastic/beats/tree/master/libbeat) and used by all [beats](https://github.com/elastic/beats). ## API Documentation @@ -57,7 +58,7 @@ ucfg allows to automatically validate fields and set defaults for fields in case ```golang // Defines struct to read config from type ExampleConfig struct { - Counter string `config:"counter" validate:"min=0, max=9"` + Counter int `config:"counter" validate:"min=0, max=9"` } // Defines default config option @@ -71,12 +72,12 @@ func main() { appConfig := defaultConfig // copy default config so it's not overwritten config, err := yaml.NewConfigWithFile(path, ucfg.PathSep(".")) if err != nil { - fmt.Fprintln(err) + fmt.Println(err) os.Exit(1) } err = config.Unpack(&appConfig) if err != nil { - fmt.Fprintln(err) + fmt.Println(err) os.Exit(1) } } diff --git a/vendor/github.com/elastic/go-ucfg/diff/keys.go b/vendor/github.com/elastic/go-ucfg/diff/keys.go new file mode 100644 index 00000000000..1612d3a7380 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/diff/keys.go @@ -0,0 +1,122 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package diff + +import ( + "fmt" + "strings" + + ucfg "github.com/elastic/go-ucfg" +) + +// Type custom type to identify what was added, remove or keep in the configuration +type Type int + +const ( + // Remove keys no longer present in the config + Remove Type = iota + + // Add keys added from the first config + Add + + // Keep keys present in both config + Keep +) + +func (dt Type) String() string { + return []string{ + "-", + "+", + " ", + }[dt] +} + +// Diff format of a diff +type Diff map[Type][]string + +// String return a human friendly format of the diff +func (d Diff) String() string { + var lines []string + + for k, values := range d { + for _, v := range values { + lines = append(lines, fmt.Sprintf("%s | key: %s", k, v)) + } + } + + return strings.Join(lines, "\n") +} + +// HasChanged returns true if we have remove of added new elements in the graph +func (d *Diff) HasChanged() bool { + if d.HasKeyAdded() || d.HasKeyRemoved() { + return true + } + return false +} + +// HasKeyRemoved returns true if not all keys are present in both configuration +func (d *Diff) HasKeyRemoved() bool { + if len((*d)[Remove]) > 0 { + return true + } + + return false +} + +// HasKeyAdded returns true if key were added in the new configuration +func (d *Diff) HasKeyAdded() bool { + if len((*d)[Add]) > 0 { + return true + } + return false +} + +// GoStringer implement the GoStringer interface +func (d Diff) GoStringer() string { + return d.String() +} + +// CompareConfigs takes two configuration and return the difference between the defined keys +func CompareConfigs(old, new *ucfg.Config, opts ...ucfg.Option) Diff { + oldKeys := old.FlattenedKeys(opts...) + newKeys := new.FlattenedKeys(opts...) + + difference := make(map[string]Type) + + // Map for candidates check + for _, k := range oldKeys { + difference[k] = Remove + } + + for _, nk := range newKeys { + if _, ok := difference[nk]; ok { + difference[nk] = Keep + } else { + difference[nk] = Add + } + } + + invert := make(Diff) + + for k, v := range difference { + invert[v] = append(invert[v], k) + } + + return invert +} diff --git a/vendor/github.com/elastic/go-ucfg/flag/value.go b/vendor/github.com/elastic/go-ucfg/flag/value.go index f75816badb7..45297edca0f 100644 --- a/vendor/github.com/elastic/go-ucfg/flag/value.go +++ b/vendor/github.com/elastic/go-ucfg/flag/value.go @@ -22,7 +22,7 @@ import ( "strings" "github.com/elastic/go-ucfg" - "github.com/elastic/go-ucfg/internal/parse" + "github.com/elastic/go-ucfg/parse" ) // NewFlagKeyValue implements the flag.Value interface for diff --git a/vendor/github.com/elastic/go-ucfg/hjson/hjson.go b/vendor/github.com/elastic/go-ucfg/hjson/hjson.go new file mode 100644 index 00000000000..24d7ad8976d --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/hjson/hjson.go @@ -0,0 +1,49 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package hjson + +import ( + "io/ioutil" + + "gopkg.in/hjson/hjson-go.v3" + + "github.com/elastic/go-ucfg" +) + +// NewConfig creates a new configuration object from the HJSON string passed via in. +func NewConfig(in []byte, opts ...ucfg.Option) (*ucfg.Config, error) { + var m interface{} + if err := hjson.Unmarshal(in, &m); err != nil { + return nil, err + } + + return ucfg.NewFrom(m, opts...) +} + +// NewConfigWithFile loads a new configuration object from an external HJSON file. +func NewConfigWithFile(name string, opts ...ucfg.Option) (*ucfg.Config, error) { + input, err := ioutil.ReadFile(name) + if err != nil { + return nil, err + } + + opts = append([]ucfg.Option{ + ucfg.MetaData(ucfg.Meta{Source: name}), + }, opts...) + return NewConfig(input, opts...) +} diff --git a/vendor/github.com/elastic/go-ucfg/initializer.go b/vendor/github.com/elastic/go-ucfg/initializer.go new file mode 100644 index 00000000000..3614f3fd90c --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/initializer.go @@ -0,0 +1,59 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "reflect" +) + +// Initializer interface provides initialization of default values support to Unpack. +// The InitDefaults method will be executed for any type passed directly or indirectly to +// Unpack. +type Initializer interface { + InitDefaults() +} + +func tryInitDefaults(val reflect.Value) reflect.Value { + t := val.Type() + + var initializer Initializer + if t.Implements(iInitializer) { + initializer = val.Interface().(Initializer) + initializer.InitDefaults() + return val + } else if reflect.PtrTo(t).Implements(iInitializer) { + tmp := pointerize(reflect.PtrTo(t), t, val) + initializer = tmp.Interface().(Initializer) + initializer.InitDefaults() + + // Return the element in the pointer so the value is set into the + // field and not a pointer to the value. + return tmp.Elem() + } + return val +} + +func hasInitDefaults(t reflect.Type) bool { + if t.Implements(iInitializer) { + return true + } + if reflect.PtrTo(t).Implements(iInitializer) { + return true + } + return false +} diff --git a/vendor/github.com/elastic/go-ucfg/opts.go b/vendor/github.com/elastic/go-ucfg/opts.go index 565adef6900..ba954242829 100644 --- a/vendor/github.com/elastic/go-ucfg/opts.go +++ b/vendor/github.com/elastic/go-ucfg/opts.go @@ -19,6 +19,8 @@ package ucfg import ( "os" + + "github.com/elastic/go-ucfg/parse" ) // Option type implementing additional options to be passed @@ -31,7 +33,7 @@ type options struct { pathSep string meta *Meta env []*Config - resolvers []func(name string) (string, error) + resolvers []func(name string) (string, parse.Config, error) varexp bool noParse bool @@ -104,7 +106,7 @@ func Env(e *Config) Option { // 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 { +func Resolve(fn func(name string) (string, parse.Config, error)) Option { return func(o *options) { o.resolvers = append(o.resolvers, fn) } @@ -115,12 +117,12 @@ func Resolve(fn func(name string) (string, error)) Option { var ResolveEnv Option = doResolveEnv func doResolveEnv(o *options) { - o.resolvers = append(o.resolvers, func(name string) (string, error) { + o.resolvers = append(o.resolvers, func(name string) (string, parse.Config, error) { value := os.Getenv(name) if value == "" { - return "", ErrMissing + return "", parse.EnvConfig, ErrMissing } - return value, nil + return value, parse.EnvConfig, nil }) } @@ -132,8 +134,8 @@ func doResolveEnv(o *options) { var ResolveNOOP Option = doResolveNOOP func doResolveNOOP(o *options) { - o.resolvers = append(o.resolvers, func(name string) (string, error) { - return "${" + name + "}", nil + o.resolvers = append(o.resolvers, func(name string) (string, parse.Config, error) { + return "${" + name + "}", parse.NoopConfig, nil }) } diff --git a/vendor/github.com/elastic/go-ucfg/internal/parse/parse.go b/vendor/github.com/elastic/go-ucfg/parse/parse.go similarity index 73% rename from vendor/github.com/elastic/go-ucfg/internal/parse/parse.go rename to vendor/github.com/elastic/go-ucfg/parse/parse.go index d8a6c649dfe..5fafefd9a30 100644 --- a/vendor/github.com/elastic/go-ucfg/internal/parse/parse.go +++ b/vendor/github.com/elastic/go-ucfg/parse/parse.go @@ -25,8 +25,41 @@ import ( "unicode" ) +// Config allows enabling and disabling parser features. +type Config struct { + Array bool + Object bool + StringDQuote bool + StringSQuote bool +} + +// DefaultConfig is the default config with all parser features enabled. +var DefaultConfig = Config{ + Array: true, + Object: true, + StringDQuote: true, + StringSQuote: true, +} + +// EnvConfig is configuration for parser when the value comes from environmental variable. +var EnvConfig = Config{ + Array: true, + Object: false, + StringDQuote: true, + StringSQuote: true, +} + +// NoopConfig is configuration for parser that disables all options. +var NoopConfig = Config{ + Array: false, + Object: false, + StringDQuote: false, + StringSQuote: false, +} + type flagParser struct { input string + cfg Config } // stopSet definitions for handling unquoted strings @@ -42,17 +75,38 @@ const ( // // 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: +// string 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 +// 3. strings without quotes. String parsing stops at // special characters like '[]{},:' // // In addition, top-level values can be separated by ',' to build arrays // without having to use []. func Value(content string) (interface{}, error) { - p := &flagParser{strings.TrimSpace(content)} + return ValueWithConfig(content, DefaultConfig) +} + +// ValueWithConfig parses command line arguments, supporting +// boolean, numbers, strings, arrays, objects when enabled. +// +// 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 +// string 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 at +// special characters like '[]{},:' +// +// In addition, top-level values can be separated by ',' to build arrays +// without having to use []. +func ValueWithConfig(content string, cfg Config) (interface{}, error) { + p := &flagParser{strings.TrimSpace(content), cfg} + if err := p.validateConfig(); err != nil { + return nil, err + } v, err := p.parse() if err != nil { return nil, fmt.Errorf("%v when parsing '%v'", err.Error(), content) @@ -60,6 +114,13 @@ func Value(content string) (interface{}, error) { return v, nil } +func (p *flagParser) validateConfig() error { + if !p.cfg.Array && p.cfg.Object { + return fmt.Errorf("cfg.Array cannot be disabled when cfg.Object is enabled") + } + return nil +} + func (p *flagParser) parse() (interface{}, error) { var values []interface{} @@ -99,13 +160,25 @@ func (p *flagParser) parseValue(stopSet string) (interface{}, error) { switch in[0] { case '[': - return p.parseArray() + if p.cfg.Array { + return p.parseArray() + } + return p.parsePrimitive(stopSet) case '{': - return p.parseObj() + if p.cfg.Object { + return p.parseObj() + } + return p.parsePrimitive(stopSet) case '"': - return p.parseStringDQuote() + if p.cfg.StringDQuote { + return p.parseStringDQuote() + } + return p.parsePrimitive(stopSet) case '\'': - return p.parseStringSQuote() + if p.cfg.StringSQuote { + return p.parseStringSQuote() + } + return p.parsePrimitive(stopSet) default: return p.parsePrimitive(stopSet) } diff --git a/vendor/github.com/elastic/go-ucfg/reify.go b/vendor/github.com/elastic/go-ucfg/reify.go index c209964b883..ad14f95baf7 100644 --- a/vendor/github.com/elastic/go-ucfg/reify.go +++ b/vendor/github.com/elastic/go-ucfg/reify.go @@ -21,8 +21,6 @@ import ( "reflect" "regexp" "time" - "unicode" - "unicode/utf8" ) // Unpack unpacks c into a struct, a map, or a slice allocating maps, slices, @@ -85,8 +83,21 @@ import ( // The struct tag options `replace`, `append`, and `prepend` overwrites the // global value merging strategy (e.g. ReplaceValues, AppendValues, ...) for all sub-fields. // +// When unpacking into a map, primitive, or struct Unpack will call InitDefaults if +// the type implements the Initializer interface. The Initializer interface is not supported +// on arrays or slices. InitDefaults is initialized top-down, meaning that if struct contains +// a map, struct, or primitive that also implements the Initializer interface the contained +// type will be initialized after the struct that contains it. (e.g. if we have +// type A struct { B B }, with both A, and B implementing InitDefaults, then A.InitDefaults +// is called before B.InitDefaults). In the case that a struct contains a pointer to +// a type that implements the Initializer interface and the configuration doesn't contain a +// value for that field then the pointer will not be initialized and InitDefaults will not +// be called. +// // 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. +// be touched by Unpack unless they are initialized from InitDefaults. Those values will +// be validated using the same rules below just as if the values came from the configuration. +// This gives the requirement that pre-filled in values or defaults must also validate. // // 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. @@ -180,14 +191,19 @@ func reifyMap(opts *options, to reflect.Value, from *Config) Error { return raiseKeyInvalidTypeUnpack(to.Type(), from) } + if to.IsNil() { + to.Set(reflect.MakeMap(to.Type())) + } + tryInitDefaults(to) + fields := from.fields.dict() if len(fields) == 0 { + if err := tryRecursiveValidate(to, opts, nil); err != nil { + return raiseValidation(from.ctx, from.metadata, "", err) + } return nil } - if to.IsNil() { - to.Set(reflect.MakeMap(to.Type())) - } for k, value := range fields { opts.activeFields = newFieldSet(parentFields) key := reflect.ValueOf(k) @@ -208,6 +224,10 @@ func reifyMap(opts *options, to reflect.Value, from *Config) Error { to.SetMapIndex(key, v) } + if err := tryValidate(to); err != nil { + return raiseValidation(from.ctx, from.metadata, "", err) + } + return nil } @@ -228,57 +248,38 @@ func reifyStruct(opts *options, orig reflect.Value, cfg *Config) Error { return err } } else { + tryInitDefaults(to) numField := to.NumField() for i := 0; i < numField; i++ { - stField := to.Type().Field(i) - - // ignore non exported fields - if rune, _ := utf8.DecodeRuneInString(stField.Name); !unicode.IsUpper(rune) { - continue + fInfo, skip, err := accessField(to, i, opts) + if err != nil { + return err } - name, tagOpts := parseTags(stField.Tag.Get(opts.tag)) - if tagOpts.ignore { + if skip { continue } - // create new context, overwriting configValueHandling for all sub-operations - if tagOpts.cfgHandling != opts.configValueHandling { - tmp := &options{} - *tmp = *opts - tmp.configValueHandling = tagOpts.cfgHandling - opts = tmp - } - - opts.activeFields = newFieldSet(parentFields) - - vField := to.Field(i) - validators, err := parseValidatorTags(stField.Tag.Get(opts.validatorTag)) - if err != nil { - return raiseCritical(err, "") - } - - if tagOpts.squash { - vField := chaseValue(vField) + if fInfo.tagOptions.squash { + vField := chaseValue(fInfo.value) switch vField.Kind() { case reflect.Struct, reflect.Map: - if err := reifyInto(opts, vField, cfg); err != nil { + if err := reifyInto(fInfo.options, fInfo.value, cfg); err != nil { return err } case reflect.Slice, reflect.Array: - fopts := fieldOptions{opts: opts, tag: tagOpts, validators: validators} - v, err := reifyMergeValue(fopts, vField, cfgSub{cfg}) + fopts := fieldOptions{opts: fInfo.options, tag: fInfo.tagOptions, validators: fInfo.validatorTags} + v, err := reifyMergeValue(fopts, fInfo.value, cfgSub{cfg}) if err != nil { return err } vField.Set(v) default: - return raiseInlineNeedsObject(cfg, stField.Name, vField.Type()) + return raiseInlineNeedsObject(cfg, fInfo.name, fInfo.value.Type()) } } else { - name = fieldName(name, stField.Name) - fopts := fieldOptions{opts: opts, tag: tagOpts, validators: validators} - if err := reifyGetField(cfg, fopts, name, vField); err != nil { + fopts := fieldOptions{opts: fInfo.options, tag: fInfo.tagOptions, validators: fInfo.validatorTags} + if err := reifyGetField(cfg, fopts, fInfo.name, fInfo.value, fInfo.ftype); err != nil { return err } } @@ -298,6 +299,7 @@ func reifyGetField( opts fieldOptions, name string, to reflect.Value, + fieldType reflect.Type, ) Error { p := parsePath(name, opts.opts.pathSep) value, err := p.GetValue(cfg, opts.opts) @@ -309,10 +311,28 @@ func reifyGetField( } if isNil(value) { - if err := runValidators(nil, opts.validators); err != nil { - return raiseValidation(cfg.ctx, cfg.metadata, name, err) + // When fieldType is a pointer and the value is nil, return nil as the + // underlying type should not be allocated. + if fieldType.Kind() == reflect.Ptr { + if err := tryRecursiveValidate(to, opts.opts, opts.validators); err != nil { + return raiseValidation(cfg.ctx, cfg.metadata, name, err) + } + return nil + } + + // Primitive types return early when it doesn't implement the Initializer interface. + if fieldType.Kind() != reflect.Map && fieldType.Kind() != reflect.Struct && !hasInitDefaults(fieldType) { + if err := tryRecursiveValidate(to, opts.opts, opts.validators); err != nil { + return raiseValidation(cfg.ctx, cfg.metadata, name, err) + } + return nil + } + + // None primitive types always get initialized even if it doesn't implement the + // Initializer interface, because nested types might implement the Initializer interface. + if value == nil { + value = &cfgNil{cfgPrimitive{cfg.ctx, cfg.metadata}} } - return nil } v, err := reifyMergeValue(opts, to, value) @@ -320,7 +340,7 @@ func reifyGetField( return err } - to.Set(v) + to.Set(pointerize(to.Type(), v.Type(), v)) return nil } @@ -561,15 +581,20 @@ func reifyDoArray( val value, arr []value, ) (reflect.Value, Error) { - idx := start - for _, from := range arr { - to.Index(idx) - v, err := reifyMergeValue(opts, to.Index(idx), from) - if err != nil { - return reflect.Value{}, err + aLen := len(arr) + tLen := to.Len() + for idx := 0; idx < tLen; idx++ { + if idx >= start && idx < start+aLen { + v, err := reifyMergeValue(opts, to.Index(idx), arr[idx-start]) + if err != nil { + return reflect.Value{}, err + } + to.Index(idx).Set(v) + } else { + if err := tryRecursiveValidate(to.Index(idx), opts.opts, nil); err != nil { + return reflect.Value{}, raiseValidation(val.Context(), val.meta(), "", err) + } } - to.Index(idx).Set(v) - idx++ } if err := runValidators(to.Interface(), opts.validators); err != nil { @@ -577,6 +602,11 @@ func reifyDoArray( return reflect.Value{}, raiseValidation(ctx, val.meta(), "", err) } + if err := tryValidate(to); err != nil { + ctx := val.Context() + return reflect.Value{}, raiseValidation(ctx, val.meta(), "", err) + } + return to, nil } @@ -615,7 +645,8 @@ func reifyPrimitive( ) (reflect.Value, Error) { // zero initialize value if val==nil if isNil(val) { - return pointerize(t, baseType, reflect.Zero(baseType)), nil + v := pointerize(t, baseType, reflect.Zero(baseType)) + return tryInitDefaults(v), nil } var v reflect.Value diff --git a/vendor/github.com/elastic/go-ucfg/types.go b/vendor/github.com/elastic/go-ucfg/types.go index a5b9d3eb58e..13d601c9d86 100644 --- a/vendor/github.com/elastic/go-ucfg/types.go +++ b/vendor/github.com/elastic/go-ucfg/types.go @@ -26,7 +26,7 @@ import ( "sync/atomic" "time" - "github.com/elastic/go-ucfg/internal/parse" + "github.com/elastic/go-ucfg/parse" ) type value interface { @@ -523,7 +523,7 @@ func (r *refDynValue) getValue( } previousErr := err - str, err := ref.resolveEnv(p.ctx.getParent(), opts) + str, parseCfg, err := ref.resolveEnv(p.ctx.getParent(), opts) if err != nil { // TODO(ph): Not everything is an Error, will do some cleanup in another PR. if v, ok := previousErr.(Error); ok { @@ -533,7 +533,7 @@ func (r *refDynValue) getValue( } return nil, err } - return parseValue(p, opts, str) + return parseValue(p, opts, str, parseCfg) } func (s spliceDynValue) getValue( @@ -546,19 +546,19 @@ func (s spliceDynValue) getValue( return nil, err } - return parseValue(p, opts, str) + return parseValue(p, opts, str, parse.DefaultConfig) } func (s spliceDynValue) String() string { return "" } -func parseValue(p *cfgPrimitive, opts *options, str string) (value, error) { +func parseValue(p *cfgPrimitive, opts *options, str string, parseCfg parse.Config) (value, error) { if opts.noParse { return nil, raiseNoParse(p.ctx, p.meta()) } - ifc, err := parse.Value(str) + ifc, err := parse.ValueWithConfig(str, parseCfg) if err != nil { return nil, err } diff --git a/vendor/github.com/elastic/go-ucfg/ucfg.go b/vendor/github.com/elastic/go-ucfg/ucfg.go index 69f6eaab2d6..bddd42333f1 100644 --- a/vendor/github.com/elastic/go-ucfg/ucfg.go +++ b/vendor/github.com/elastic/go-ucfg/ucfg.go @@ -63,8 +63,9 @@ var ( tInterfaceArray = reflect.TypeOf([]interface{}(nil)) // interface types - tError = reflect.TypeOf((*error)(nil)).Elem() - tValidator = reflect.TypeOf((*Validator)(nil)).Elem() + tError = reflect.TypeOf((*error)(nil)).Elem() + iInitializer = reflect.TypeOf((*Initializer)(nil)).Elem() + tValidator = reflect.TypeOf((*Validator)(nil)).Elem() // primitives tBool = reflect.TypeOf(true) diff --git a/vendor/github.com/elastic/go-ucfg/unpack.go b/vendor/github.com/elastic/go-ucfg/unpack.go index 85ba18176e3..00fb92c9376 100644 --- a/vendor/github.com/elastic/go-ucfg/unpack.go +++ b/vendor/github.com/elastic/go-ucfg/unpack.go @@ -159,6 +159,11 @@ func implementsUnpacker(t reflect.Type) bool { } func unpackWith(opts *options, v reflect.Value, with value) Error { + // short circuit nil values + if isNil(with) { + return nil + } + ctx := with.Context() meta := with.meta() diff --git a/vendor/github.com/elastic/go-ucfg/util.go b/vendor/github.com/elastic/go-ucfg/util.go index 601cbd11a68..0e7080ab0a5 100644 --- a/vendor/github.com/elastic/go-ucfg/util.go +++ b/vendor/github.com/elastic/go-ucfg/util.go @@ -20,6 +20,8 @@ package ucfg import ( "reflect" "strings" + "unicode" + "unicode/utf8" ) type tagOptions struct { @@ -172,3 +174,47 @@ func isFloat(k reflect.Kind) bool { return false } } + +type fieldInfo struct { + name string + ftype reflect.Type + value reflect.Value + options *options + tagOptions tagOptions + validatorTags []validatorTag +} + +func accessField(structVal reflect.Value, fieldIdx int, opts *options) (fieldInfo, bool, Error) { + stField := structVal.Type().Field(fieldIdx) + + // ignore non exported fields + if rune, _ := utf8.DecodeRuneInString(stField.Name); !unicode.IsUpper(rune) { + return fieldInfo{}, true, nil + } + name, tagOpts := parseTags(stField.Tag.Get(opts.tag)) + if tagOpts.ignore { + return fieldInfo{}, true, nil + } + + // create new context, overwriting configValueHandling for all sub-operations + if tagOpts.cfgHandling != opts.configValueHandling { + tmp := &options{} + *tmp = *opts + tmp.configValueHandling = tagOpts.cfgHandling + opts = tmp + } + + validators, err := parseValidatorTags(stField.Tag.Get(opts.validatorTag)) + if err != nil { + return fieldInfo{}, false, raiseCritical(err, "") + } + + return fieldInfo{ + name: fieldName(name, stField.Name), + ftype: stField.Type, + value: structVal.Field(fieldIdx), + options: opts, + tagOptions: tagOpts, + validatorTags: validators, + }, false, nil +} diff --git a/vendor/github.com/elastic/go-ucfg/validator.go b/vendor/github.com/elastic/go-ucfg/validator.go index 54c15a4197b..b56e8835dfd 100644 --- a/vendor/github.com/elastic/go-ucfg/validator.go +++ b/vendor/github.com/elastic/go-ucfg/validator.go @@ -110,6 +110,10 @@ func tryValidate(val reflect.Value) error { t := val.Type() var validator Validator + if (t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface) && val.IsNil() { + return nil + } + if t.Implements(tValidator) { validator = val.Interface().(Validator) } else if reflect.PtrTo(t).Implements(tValidator) { @@ -124,6 +128,9 @@ func tryValidate(val reflect.Value) error { } func runValidators(val interface{}, validators []validatorTag) error { + if validators == nil { + return nil + } for _, tag := range validators { if err := tag.cb(val, tag.param); err != nil { return err @@ -132,6 +139,76 @@ func runValidators(val interface{}, validators []validatorTag) error { return nil } +func tryRecursiveValidate(val reflect.Value, opts *options, validators []validatorTag) error { + var curr interface{} + if val.IsValid() { + curr = val.Interface() + } + if err := runValidators(curr, validators); err != nil { + return err + } + if !val.IsValid() { + return nil + } + + t := val.Type() + if (t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface) && val.IsNil() { + return nil + } + + var err error + switch chaseValue(val).Kind() { + case reflect.Struct: + err = validateStruct(val, opts) + case reflect.Map: + err = validateMap(val, opts) + case reflect.Array, reflect.Slice: + err = validateArray(val, opts) + } + + if err != nil { + return err + } + return tryValidate(val) +} + +func validateStruct(val reflect.Value, opts *options) error { + val = chaseValue(val) + numField := val.NumField() + for i := 0; i < numField; i++ { + fInfo, skip, err := accessField(val, i, opts) + if err != nil { + return err + } + if skip { + continue + } + + if err := tryRecursiveValidate(fInfo.value, fInfo.options, fInfo.validatorTags); err != nil { + return err + } + } + return nil +} + +func validateMap(val reflect.Value, opts *options) error { + for _, key := range val.MapKeys() { + if err := tryRecursiveValidate(val.MapIndex(key), opts, nil); err != nil { + return err + } + } + return nil +} + +func validateArray(val reflect.Value, opts *options) error { + for i := 0; i < val.Len(); i++ { + if err := tryRecursiveValidate(val.Index(i), opts, nil); err != nil { + return err + } + } + return nil +} + // validateNonZero implements the `nonzero` validation tag. // If nonzero is set, the validator is only run if field is present in config. // It checks for numbers and durations to be != 0, and for strings/arrays/slices @@ -148,7 +225,7 @@ func validateNonZero(v interface{}, name string) error { return nil } - val := reflect.ValueOf(v) + val := chaseValue(reflect.ValueOf(v)) switch val.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if val.Int() != 0 { @@ -305,7 +382,20 @@ func validateRequired(v interface{}, name string) error { if v == nil { return ErrRequired } - return validateNonEmpty(v, name) + val := reflect.ValueOf(v) + if val.Kind() == reflect.Ptr && val.IsNil() { + return ErrRequired + } + if isInt(val.Kind()) || isUint(val.Kind()) || isFloat(val.Kind()) { + if err := validateNonZero(v, name); err != nil { + return ErrRequired + } + return nil + } + if err := validateNonEmpty(v, name); err != nil { + return ErrRequired + } + return nil } func validateNonEmpty(v interface{}, _ string) error { diff --git a/vendor/github.com/elastic/go-ucfg/variables.go b/vendor/github.com/elastic/go-ucfg/variables.go index 3a76d2114ff..791bee3d3eb 100644 --- a/vendor/github.com/elastic/go-ucfg/variables.go +++ b/vendor/github.com/elastic/go-ucfg/variables.go @@ -22,6 +22,8 @@ import ( "errors" "fmt" "strings" + + "github.com/elastic/go-ucfg/parse" ) type reference struct { @@ -140,22 +142,23 @@ func (r *reference) resolveRef(cfg *Config, opts *options) (value, error) { return nil, err } -func (r *reference) resolveEnv(cfg *Config, opts *options) (string, error) { +func (r *reference) resolveEnv(cfg *Config, opts *options) (string, parse.Config, error) { var err error if len(opts.resolvers) > 0 { key := r.Path.String() for i := len(opts.resolvers) - 1; i >= 0; i-- { var v string + var cfg parse.Config resolver := opts.resolvers[i] - v, err = resolver(key) + v, cfg, err = resolver(key) if err == nil { - return v, nil + return v, cfg, nil } } } - return "", err + return "", parse.DefaultConfig, err } func (r *reference) resolve(cfg *Config, opts *options) (value, error) { @@ -166,7 +169,7 @@ func (r *reference) resolve(cfg *Config, opts *options) (value, error) { previousErr := err - s, err := r.resolveEnv(cfg, opts) + s, _, err := r.resolveEnv(cfg, opts) if err != nil { // TODO(ph): Not everything is an Error, will do some cleanup in another PR. if v, ok := previousErr.(Error); ok { diff --git a/vendor/github.com/elastic/go-ucfg/yaml/yaml.go b/vendor/github.com/elastic/go-ucfg/yaml/yaml.go index 4ce5e08fe0a..772423f99bb 100644 --- a/vendor/github.com/elastic/go-ucfg/yaml/yaml.go +++ b/vendor/github.com/elastic/go-ucfg/yaml/yaml.go @@ -35,7 +35,7 @@ func NewConfig(in []byte, opts ...ucfg.Option) (*ucfg.Config, error) { return ucfg.NewFrom(m, opts...) } -// NewConfigWithFile loads a new configuration object from an external JSON file. +// NewConfigWithFile loads a new configuration object from an external YAML file. func NewConfigWithFile(name string, opts ...ucfg.Option) (*ucfg.Config, error) { input, err := ioutil.ReadFile(name) if err != nil { diff --git a/vendor/gopkg.in/hjson/hjson-go.v3/LICENSE b/vendor/gopkg.in/hjson/hjson-go.v3/LICENSE new file mode 100644 index 00000000000..3049b054340 --- /dev/null +++ b/vendor/gopkg.in/hjson/hjson-go.v3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016, 2017 Christian Zangl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gopkg.in/hjson/hjson-go.v3/README.md b/vendor/gopkg.in/hjson/hjson-go.v3/README.md new file mode 100644 index 00000000000..e5bed4ba722 --- /dev/null +++ b/vendor/gopkg.in/hjson/hjson-go.v3/README.md @@ -0,0 +1,181 @@ +# hjson-go + +[![Build Status](https://img.shields.io/travis/hjson/hjson-go.svg?style=flat-square)](http://travis-ci.org/hjson/hjson-go) +[![Go Pkg](https://img.shields.io/github/release/hjson/hjson-go.svg?style=flat-square&label=go-pkg)](https://github.com/hjson/hjson-go/releases) +[![Go Report Card](https://goreportcard.com/badge/github.com/hjson/hjson-go?style=flat-square)](https://goreportcard.com/report/github.com/hjson/hjson-go) +[![coverage](https://img.shields.io/badge/coverage-ok-brightgreen.svg?style=flat-square)](http://gocover.io/github.com/hjson/hjson-go/) +[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/hjson/hjson-go) + +![Hjson Intro](http://hjson.org/hjson1.gif) + +``` +{ + # specify rate in requests/second (because comments are helpful!) + rate: 1000 + + // prefer c-style comments? + /* feeling old fashioned? */ + + # did you notice that rate doesn't need quotes? + hey: look ma, no quotes for strings either! + + # best of all + notice: [] + anything: ? + + # yes, commas are optional! +} +``` + +The Go implementation of Hjson is based on [hjson-js](https://github.com/hjson/hjson-js). For other platforms see [hjson.org](http://hjson.org). + +# Install + +Make sure you have a working Go environment. See the [install instructions](http://golang.org/doc/install.html). + +1. Get the sources +```bash +$ go get -u github.com/hjson/hjson-go +``` +2. Build the **hjson-cli** commandline tool (optional) +```bash +$ cd $(go env GOPATH)/src/github.com/hjson/hjson-go/hjson-cli && go install +$ hjson-cli --version +``` +# Usage as command line tool +``` +usage: hjson-cli [OPTIONS] [INPUT] +hjson can be used to convert JSON from/to Hjson. + +hjson will read the given JSON/Hjson input file or read from stdin. + +Options: + -allowMinusZero + Allow -0. + -bracesSameLine + Print braces on the same line. + -c Output as JSON. + -h Show this screen. + -indentBy string + The indent string. (default " ") + -j Output as formatted JSON. + -omitRootBraces + Omit braces at the root. + -quoteAlways + Always quote string values. +``` + +Sample: +- run `hjson-cli test.json > test.hjson` to convert to Hjson +- run `hjson-cli -j test.hjson > test.json` to convert to JSON + +# Usage as a GO library + +```go + +package main + +import ( + "github.com/hjson/hjson-go" + "fmt" +) + +func main() { + + // Now let's look at decoding Hjson data into Go + // values. + sampleText := []byte(` + { + # specify rate in requests/second + rate: 1000 + array: + [ + foo + bar + ] + }`) + + // We need to provide a variable where Hjson + // can put the decoded data. + var dat map[string]interface{} + + // Decode and a check for errors. + if err := hjson.Unmarshal(sampleText, &dat); err != nil { + panic(err) + } + fmt.Println(dat) + + // In order to use the values in the decoded map, + // we'll need to cast them to their appropriate type. + + rate := dat["rate"].(float64) + fmt.Println(rate) + + array := dat["array"].([]interface{}) + str1 := array[0].(string) + fmt.Println(str1) + + + // To encode to Hjson with default options: + sampleMap := map[string]int{"apple": 5, "lettuce": 7} + hjson, _ := hjson.Marshal(sampleMap) + // this is short for: + // options := hjson.DefaultOptions() + // hjson, _ := hjson.MarshalWithOptions(sampleMap, options) + fmt.Println(string(hjson)) +} +``` + +If you prefer, you can also unmarshal to Go objects by converting to JSON: + +```go + +package main + +import ( + "github.com/hjson/hjson-go" + "encoding/json" + "fmt" +) + +type Sample struct { + Rate int + Array []string +} + +func main() { + + sampleText := []byte(` + { + # specify rate in requests/second + rate: 1000 + array: + [ + foo + bar + ] + }`) + + // read Hjson + var dat map[string]interface{} + hjson.Unmarshal(sampleText, &dat) + + // convert to JSON + b, _ := json.Marshal(dat) + + // unmarshal + var sample Sample + json.Unmarshal(b, &sample) + + fmt.Println(sample.Rate) + fmt.Println(sample.Array) +} +``` + +# API + +[![godoc](https://godoc.org/github.com/hjson/hjson-go?status.svg)](http://godoc.org/github.com/hjson/hjson-go) + +# History + +[see releases](https://github.com/hjson/hjson-go/releases) diff --git a/vendor/gopkg.in/hjson/hjson-go.v3/build_release.sh b/vendor/gopkg.in/hjson/hjson-go.v3/build_release.sh new file mode 100755 index 00000000000..9c5e3091365 --- /dev/null +++ b/vendor/gopkg.in/hjson/hjson-go.v3/build_release.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +cd `dirname $0` +ROOT=$PWD/_dist + +if [ -d "$ROOT" ]; then rm -rf $ROOT; fi + +mkdir $ROOT + +function build() { + export GOOS=$1 + export GOARCH=$2 + + echo build $GOOS $GOARCH + OUT=$ROOT/${GOOS}_${GOARCH} + mkdir $OUT + cd $OUT + go build github.com/hjson/hjson-go/hjson-cli + if [[ $3 == "zip" ]]; then + mv $OUT/hjson-cli.exe $OUT/hjson.exe + zip -j ${OUT}.zip $OUT/* + else + mv $OUT/hjson-cli $OUT/hjson + tar -czf ${OUT}.tar.gz -C $OUT . + fi + +} + +# not all targets can be built on travis + +# build android arm +build darwin 386 +build darwin amd64 +# build darwin arm +# build darwin arm64 +build dragonfly amd64 +build freebsd 386 +build freebsd amd64 +build freebsd arm +build linux 386 +build linux amd64 +build linux arm +build linux arm64 +build linux mips64 +build linux mips64le +build linux ppc64 +build linux ppc64le +build netbsd 386 +build netbsd amd64 +build netbsd arm +build openbsd 386 +build openbsd amd64 +build openbsd arm +build plan9 386 +build plan9 amd64 +build solaris amd64 +build windows 386 zip +build windows amd64 zip diff --git a/vendor/gopkg.in/hjson/hjson-go.v3/decode.go b/vendor/gopkg.in/hjson/hjson-go.v3/decode.go new file mode 100644 index 00000000000..701eaa08367 --- /dev/null +++ b/vendor/gopkg.in/hjson/hjson-go.v3/decode.go @@ -0,0 +1,480 @@ +package hjson + +import ( + "bytes" + "fmt" + "reflect" + "strings" +) + +type hjsonParser struct { + data []byte + at int // The index of the current character + ch byte // The current character +} + +func (p *hjsonParser) resetAt() { + p.at = 0 + p.ch = ' ' +} + +func isPunctuatorChar(c byte) bool { + return c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == ':' +} + +func (p *hjsonParser) errAt(message string) error { + var i int + col := 0 + line := 1 + for i = p.at - 1; i > 0 && p.data[i] != '\n'; i-- { + col++ + } + for ; i > 0; i-- { + if p.data[i] == '\n' { + line++ + } + } + samEnd := p.at - col + 20 + if samEnd > len(p.data) { + samEnd = len(p.data) + } + return fmt.Errorf("%s at line %d,%d >>> %s", message, line, col, string(p.data[p.at-col:samEnd])) +} + +func (p *hjsonParser) next() bool { + // get the next character. + if p.at < len(p.data) { + p.ch = p.data[p.at] + p.at++ + return true + } + p.ch = 0 + return false +} + +func (p *hjsonParser) peek(offs int) byte { + pos := p.at + offs + if pos >= 0 && pos < len(p.data) { + return p.data[p.at+offs] + } + return 0 +} + +var escapee = map[byte]byte{ + '"': '"', + '\'': '\'', + '\\': '\\', + '/': '/', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + 't': '\t', +} + +func (p *hjsonParser) readString(allowML bool) (string, error) { + + // Parse a string value. + res := new(bytes.Buffer) + + // callers make sure that (ch === '"' || ch === "'") + // When parsing for string values, we must look for " and \ characters. + exitCh := p.ch + for p.next() { + if p.ch == exitCh { + p.next() + if allowML && exitCh == '\'' && p.ch == '\'' && res.Len() == 0 { + // ''' indicates a multiline string + p.next() + return p.readMLString() + } else { + return res.String(), nil + } + } + if p.ch == '\\' { + p.next() + if p.ch == 'u' { + uffff := 0 + for i := 0; i < 4; i++ { + p.next() + var hex int + if p.ch >= '0' && p.ch <= '9' { + hex = int(p.ch - '0') + } else if p.ch >= 'a' && p.ch <= 'f' { + hex = int(p.ch - 'a' + 0xa) + } else if p.ch >= 'A' && p.ch <= 'F' { + hex = int(p.ch - 'A' + 0xa) + } else { + return "", p.errAt("Bad \\u char " + string(p.ch)) + } + uffff = uffff*16 + hex + } + res.WriteRune(rune(uffff)) + } else if ech, ok := escapee[p.ch]; ok { + res.WriteByte(ech) + } else { + return "", p.errAt("Bad escape \\" + string(p.ch)) + } + } else if p.ch == '\n' || p.ch == '\r' { + return "", p.errAt("Bad string containing newline") + } else { + res.WriteByte(p.ch) + } + } + return "", p.errAt("Bad string") +} + +func (p *hjsonParser) readMLString() (value string, err error) { + + // Parse a multiline string value. + res := new(bytes.Buffer) + triple := 0 + + // we are at ''' +1 - get indent + indent := 0 + for { + c := p.peek(-indent - 5) + if c == 0 || c == '\n' { + break + } + indent++ + } + + skipIndent := func() { + skip := indent + for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' && skip > 0 { + skip-- + p.next() + } + } + + // skip white/to (newline) + for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' { + p.next() + } + if p.ch == '\n' { + p.next() + skipIndent() + } + + // When parsing multiline string values, we must look for ' characters. + lastLf := false + for { + if p.ch == 0 { + return "", p.errAt("Bad multiline string") + } else if p.ch == '\'' { + triple++ + p.next() + if triple == 3 { + sres := res.Bytes() + if lastLf { + return string(sres[0 : len(sres)-1]), nil // remove last EOL + } + return string(sres), nil + } + continue + } else { + for triple > 0 { + res.WriteByte('\'') + triple-- + lastLf = false + } + } + if p.ch == '\n' { + res.WriteByte('\n') + lastLf = true + p.next() + skipIndent() + } else { + if p.ch != '\r' { + res.WriteByte(p.ch) + lastLf = false + } + p.next() + } + } +} + +func (p *hjsonParser) readKeyname() (string, error) { + + // quotes for keys are optional in Hjson + // unless they include {}[],: or whitespace. + + if p.ch == '"' || p.ch == '\'' { + return p.readString(false) + } + + name := new(bytes.Buffer) + start := p.at + space := -1 + for { + if p.ch == ':' { + if name.Len() == 0 { + return "", p.errAt("Found ':' but no key name (for an empty key name use quotes)") + } else if space >= 0 && space != name.Len() { + p.at = start + space + return "", p.errAt("Found whitespace in your key name (use quotes to include)") + } + return name.String(), nil + } else if p.ch <= ' ' { + if p.ch == 0 { + return "", p.errAt("Found EOF while looking for a key name (check your syntax)") + } + if space < 0 { + space = name.Len() + } + } else { + if isPunctuatorChar(p.ch) { + return "", p.errAt("Found '" + string(p.ch) + "' where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace)") + } + name.WriteByte(p.ch) + } + p.next() + } +} + +func (p *hjsonParser) white() { + for p.ch > 0 { + // Skip whitespace. + for p.ch > 0 && p.ch <= ' ' { + p.next() + } + // Hjson allows comments + if p.ch == '#' || p.ch == '/' && p.peek(0) == '/' { + for p.ch > 0 && p.ch != '\n' { + p.next() + } + } else if p.ch == '/' && p.peek(0) == '*' { + p.next() + p.next() + for p.ch > 0 && !(p.ch == '*' && p.peek(0) == '/') { + p.next() + } + if p.ch > 0 { + p.next() + p.next() + } + } else { + break + } + } +} + +func (p *hjsonParser) readTfnns() (interface{}, error) { + + // Hjson strings can be quoteless + // returns string, true, false, or null. + + if isPunctuatorChar(p.ch) { + return nil, p.errAt("Found a punctuator character '" + string(p.ch) + "' when expecting a quoteless string (check your syntax)") + } + chf := p.ch + value := new(bytes.Buffer) + value.WriteByte(p.ch) + + for { + p.next() + isEol := p.ch == '\r' || p.ch == '\n' || p.ch == 0 + if isEol || + p.ch == ',' || p.ch == '}' || p.ch == ']' || + p.ch == '#' || + p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') { + switch chf { + case 'f': + if strings.TrimSpace(value.String()) == "false" { + return false, nil + } + case 'n': + if strings.TrimSpace(value.String()) == "null" { + return nil, nil + } + case 't': + if strings.TrimSpace(value.String()) == "true" { + return true, nil + } + default: + if chf == '-' || chf >= '0' && chf <= '9' { + if n, err := tryParseNumber(value.Bytes(), false); err == nil { + return n, nil + } + } + } + if isEol { + // remove any whitespace at the end (ignored in quoteless strings) + return strings.TrimSpace(value.String()), nil + } + } + value.WriteByte(p.ch) + } +} + +func (p *hjsonParser) readArray() (value interface{}, err error) { + + // Parse an array value. + // assuming ch == '[' + + array := make([]interface{}, 0, 1) + + p.next() + p.white() + + if p.ch == ']' { + p.next() + return array, nil // empty array + } + + for p.ch > 0 { + var val interface{} + if val, err = p.readValue(); err != nil { + return nil, err + } + array = append(array, val) + p.white() + // in Hjson the comma is optional and trailing commas are allowed + if p.ch == ',' { + p.next() + p.white() + } + if p.ch == ']' { + p.next() + return array, nil + } + p.white() + } + + return nil, p.errAt("End of input while parsing an array (did you forget a closing ']'?)") +} + +func (p *hjsonParser) readObject(withoutBraces bool) (value interface{}, err error) { + // Parse an object value. + + object := make(map[string]interface{}) + + if !withoutBraces { + // assuming ch == '{' + p.next() + } + + p.white() + if p.ch == '}' && !withoutBraces { + p.next() + return object, nil // empty object + } + for p.ch > 0 { + var key string + if key, err = p.readKeyname(); err != nil { + return nil, err + } + p.white() + if p.ch != ':' { + return nil, p.errAt("Expected ':' instead of '" + string(p.ch) + "'") + } + p.next() + // duplicate keys overwrite the previous value + var val interface{} + if val, err = p.readValue(); err != nil { + return nil, err + } + object[key] = val + p.white() + // in Hjson the comma is optional and trailing commas are allowed + if p.ch == ',' { + p.next() + p.white() + } + if p.ch == '}' && !withoutBraces { + p.next() + return object, nil + } + p.white() + } + + if withoutBraces { + return object, nil + } + return nil, p.errAt("End of input while parsing an object (did you forget a closing '}'?)") +} + +func (p *hjsonParser) readValue() (interface{}, error) { + + // Parse a Hjson value. It could be an object, an array, a string, a number or a word. + + p.white() + switch p.ch { + case '{': + return p.readObject(false) + case '[': + return p.readArray() + case '"', '\'': + return p.readString(true) + default: + return p.readTfnns() + } +} + +func (p *hjsonParser) rootValue() (interface{}, error) { + // Braces for the root object are optional + + p.white() + switch p.ch { + case '{': + return p.checkTrailing(p.readObject(false)) + case '[': + return p.checkTrailing(p.readArray()) + } + + // assume we have a root object without braces + res, err := p.checkTrailing(p.readObject(true)) + if err == nil { + return res, nil + } + + // test if we are dealing with a single JSON value instead (true/false/null/num/"") + p.resetAt() + if res2, err2 := p.checkTrailing(p.readValue()); err2 == nil { + return res2, nil + } + return res, err +} + +func (p *hjsonParser) checkTrailing(v interface{}, err error) (interface{}, error) { + if err != nil { + return nil, err + } + p.white() + if p.ch > 0 { + return nil, p.errAt("Syntax error, found trailing characters") + } + return v, nil +} + +// Unmarshal parses the Hjson-encoded data and stores the result +// in the value pointed to by v. +// +// Unmarshal uses the inverse of the encodings that +// Marshal uses, allocating maps, slices, and pointers as necessary. +// +func Unmarshal(data []byte, v interface{}) (err error) { + var value interface{} + parser := &hjsonParser{data, 0, ' '} + parser.resetAt() + value, err = parser.rootValue() + if err != nil { + return err + } + + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return fmt.Errorf("non-pointer %v", reflect.TypeOf(v)) + } + for rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("%v", e) + } + }() + rv.Set(reflect.ValueOf(value)) + return err +} diff --git a/vendor/gopkg.in/hjson/hjson-go.v3/encode.go b/vendor/gopkg.in/hjson/hjson-go.v3/encode.go new file mode 100644 index 00000000000..e1f7d388f18 --- /dev/null +++ b/vendor/gopkg.in/hjson/hjson-go.v3/encode.go @@ -0,0 +1,470 @@ +package hjson + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "unicode/utf8" +) + +// EncoderOptions defines options for encoding to Hjson. +type EncoderOptions struct { + // End of line, should be either \n or \r\n + Eol string + // Place braces on the same line + BracesSameLine bool + // Deprecated: Hjson always emits braces + EmitRootBraces bool + // Always place string in quotes + QuoteAlways bool + // Indent string + IndentBy string + // Allow the -0 value (unlike ES6) + AllowMinusZero bool + // Encode unknown values as 'null' + UnknownAsNull bool +} + +// DefaultOptions returns the default encoding options. +func DefaultOptions() EncoderOptions { + opt := EncoderOptions{} + opt.Eol = "\n" + opt.BracesSameLine = false + opt.EmitRootBraces = true + opt.QuoteAlways = false + opt.IndentBy = " " + opt.AllowMinusZero = false + opt.UnknownAsNull = false + return opt +} + +type hjsonEncoder struct { + bytes.Buffer // output + EncoderOptions + indent int +} + +var needsEscape, needsQuotes, needsEscapeML, startsWithKeyword, needsEscapeName *regexp.Regexp + +func init() { + var commonRange = `\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}` + // needsEscape tests if the string can be written without escapes + needsEscape = regexp.MustCompile(`[\\\"\x00-\x1f` + commonRange + `]`) + // needsQuotes tests if the string can be written as a quoteless string (includes needsEscape but without \\ and \") + needsQuotes = regexp.MustCompile(`^\s|^"|^'|^#|^/\*|^//|^\{|^\}|^\[|^\]|^:|^,|\s$|[\x00-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}]`) + // needsEscapeML tests if the string can be written as a multiline string (like needsEscape but without \n, \r, \\, \", \t) + var x08Or9 = `\x08` // `\x09` for the old behavior + needsEscapeML = regexp.MustCompile(`'''|^[\s]+$|[\x00-` + x08Or9 + `\x0b\x0c\x0e-\x1f` + commonRange + `]`) + // starts with a keyword and optionally is followed by a comment + startsWithKeyword = regexp.MustCompile(`^(true|false|null)\s*((,|\]|\}|#|//|/\*).*)?$`) + needsEscapeName = regexp.MustCompile(`[,\{\[\}\]\s:#"']|//|/\*`) +} + +var meta = map[byte][]byte{ + // table of character substitutions + '\b': []byte("\\b"), + '\t': []byte("\\t"), + '\n': []byte("\\n"), + '\f': []byte("\\f"), + '\r': []byte("\\r"), + '"': []byte("\\\""), + '\\': []byte("\\\\"), +} + +func (e *hjsonEncoder) quoteReplace(text string) string { + return string(needsEscape.ReplaceAllFunc([]byte(text), func(a []byte) []byte { + c := meta[a[0]] + if c != nil { + return c + } + r, _ := utf8.DecodeRune(a) + return []byte(fmt.Sprintf("\\u%04x", r)) + })) +} + +func (e *hjsonEncoder) quote(value string, separator string, isRootObject bool) { + + // Check if we can insert this string without quotes + // see hjson syntax (must not parse as true, false, null or number) + + if len(value) == 0 { + e.WriteString(separator + `""`) + } else if e.QuoteAlways || + needsQuotes.MatchString(value) || + startsWithNumber([]byte(value)) || + startsWithKeyword.MatchString(value) { + + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can safely slap some quotes around it. + // Otherwise we first check if the string can be expressed in multiline + // format or we must replace the offending characters with safe escape + // sequences. + + if !needsEscape.MatchString(value) { + e.WriteString(separator + `"` + value + `"`) + } else if !needsEscapeML.MatchString(value) && !isRootObject { + e.mlString(value, separator) + } else { + e.WriteString(separator + `"` + e.quoteReplace(value) + `"`) + } + } else { + // return without quotes + e.WriteString(separator + value) + } +} + +func (e *hjsonEncoder) mlString(value string, separator string) { + // wrap the string into the ''' (multiline) format + + a := strings.Split(strings.Replace(value, "\r", "", -1), "\n") + + if len(a) == 1 { + // The string contains only a single line. We still use the multiline + // format as it avoids escaping the \ character (e.g. when used in a + // regex). + e.WriteString(separator + "'''") + e.WriteString(a[0]) + } else { + e.writeIndent(e.indent + 1) + e.WriteString("'''") + for _, v := range a { + indent := e.indent + 1 + if len(v) == 0 { + indent = 0 + } + e.writeIndent(indent) + e.WriteString(v) + } + e.writeIndent(e.indent + 1) + } + e.WriteString("'''") +} + +func (e *hjsonEncoder) quoteName(name string) string { + if len(name) == 0 { + return `""` + } + + // Check if we can insert this name without quotes + + if needsEscapeName.MatchString(name) { + if needsEscape.MatchString(name) { + name = e.quoteReplace(name) + } + return `"` + name + `"` + } + // without quotes + return name +} + +type sortAlpha []reflect.Value + +func (s sortAlpha) Len() int { + return len(s) +} +func (s sortAlpha) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s sortAlpha) Less(i, j int) bool { + return s[i].String() < s[j].String() +} + +func (e *hjsonEncoder) writeIndent(indent int) { + e.WriteString(e.Eol) + for i := 0; i < indent; i++ { + e.WriteString(e.IndentBy) + } +} + +func (e *hjsonEncoder) useMarshaler(value reflect.Value, separator string) error { + b, err := value.Interface().(json.Marshaler).MarshalJSON() + if err != nil { + return err + } + e.WriteString(separator) + e.WriteString(string(b)) + return nil +} + +var marshaler = reflect.TypeOf((*json.Marshaler)(nil)).Elem() + +func (e *hjsonEncoder) str(value reflect.Value, noIndent bool, separator string, isRootObject bool) error { + + // Produce a string from value. + + kind := value.Kind() + + if kind == reflect.Interface || kind == reflect.Ptr { + if value.IsNil() { + e.WriteString(separator) + e.WriteString("null") + return nil + } + value = value.Elem() + kind = value.Kind() + } + + if value.Type().Implements(marshaler) { + return e.useMarshaler(value, separator) + } + + switch kind { + case reflect.String: + e.quote(value.String(), separator, isRootObject) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + e.WriteString(separator) + e.WriteString(strconv.FormatInt(value.Int(), 10)) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Uintptr: + e.WriteString(separator) + e.WriteString(strconv.FormatUint(value.Uint(), 10)) + + case reflect.Float32, reflect.Float64: + // JSON numbers must be finite. Encode non-finite numbers as null. + e.WriteString(separator) + number := value.Float() + if math.IsInf(number, 0) || math.IsNaN(number) { + e.WriteString("null") + } else if !e.AllowMinusZero && number == -0 { + e.WriteString("0") + } else { + // find shortest representation ('G' does not work) + val := strconv.FormatFloat(number, 'f', -1, 64) + exp := strconv.FormatFloat(number, 'E', -1, 64) + if len(exp) < len(val) { + val = strings.ToLower(exp) + } + e.WriteString(val) + } + + case reflect.Bool: + e.WriteString(separator) + if value.Bool() { + e.WriteString("true") + } else { + e.WriteString("false") + } + + case reflect.Slice, reflect.Array: + + len := value.Len() + if len == 0 { + e.WriteString(separator) + e.WriteString("[]") + break + } + + indent1 := e.indent + e.indent++ + + if !noIndent && !e.BracesSameLine { + e.writeIndent(indent1) + } else { + e.WriteString(separator) + } + e.WriteString("[") + + // Join all of the element texts together, separated with newlines + for i := 0; i < len; i++ { + e.writeIndent(e.indent) + if err := e.str(value.Index(i), true, "", false); err != nil { + return err + } + } + + e.writeIndent(indent1) + e.WriteString("]") + + e.indent = indent1 + + case reflect.Map: + + len := value.Len() + if len == 0 { + e.WriteString(separator) + e.WriteString("{}") + break + } + + indent1 := e.indent + e.indent++ + if !noIndent && !e.BracesSameLine { + e.writeIndent(indent1) + } else { + e.WriteString(separator) + } + e.WriteString("{") + + keys := value.MapKeys() + sort.Sort(sortAlpha(keys)) + + // Join all of the member texts together, separated with newlines + for i := 0; i < len; i++ { + e.writeIndent(e.indent) + e.WriteString(e.quoteName(keys[i].String())) + e.WriteString(":") + if err := e.str(value.MapIndex(keys[i]), false, " ", false); err != nil { + return err + } + } + + e.writeIndent(indent1) + e.WriteString("}") + e.indent = indent1 + + case reflect.Struct: + + l := value.NumField() + if l == 0 { + e.WriteString(separator) + e.WriteString("{}") + break + } + + indent1 := e.indent + e.indent++ + if !noIndent && !e.BracesSameLine { + e.writeIndent(indent1) + } else { + e.WriteString(separator) + } + e.WriteString("{") + + // Join all of the member texts together, separated with newlines + for i := 0; i < l; i++ { + curStructField := value.Type().Field(i) + curField := value.Field(i) + + name := curStructField.Name + jsonTag := curStructField.Tag.Get("json") + jsonComment := curStructField.Tag.Get("comment") + omitEmpty := false + if jsonTag == "-" { + continue + } + splits := strings.Split(jsonTag, ",") + if splits[0] != "" { + name = splits[0] + } + if len(splits) > 1 { + for _, opt := range splits[1:] { + if opt == "omitempty" { + omitEmpty = true + } + } + } + if omitEmpty && isEmptyValue(curField) { + continue + } + if len(jsonComment) > 0 { + for _, line := range strings.Split(jsonComment, e.Eol) { + e.WriteString(separator) + e.writeIndent(e.indent) + e.WriteString(fmt.Sprintf("# %s", line)) + e.WriteString(separator) + } + } + e.writeIndent(e.indent) + e.WriteString(e.quoteName(name)) + e.WriteString(":") + if err := e.str(curField, false, " ", false); err != nil { + return err + } + if len(jsonComment) > 0 && i < l-1 { + e.WriteString(e.Eol) + } + } + + e.writeIndent(indent1) + e.WriteString("}") + + e.indent = indent1 + + default: + if e.UnknownAsNull { + // Use null as a placeholder for non-JSON values. + e.WriteString("null") + } else { + return errors.New("Unsupported type " + value.Type().String()) + } + } + return nil +} + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + default: + return false + } +} + +// Marshal returns the Hjson encoding of v using +// default options. +// +// See MarshalWithOptions. +// +func Marshal(v interface{}) ([]byte, error) { + return MarshalWithOptions(v, DefaultOptions()) +} + +// MarshalWithOptions returns the Hjson encoding of v. +// +// Marshal traverses the value v recursively. +// +// Boolean values encode as JSON booleans. +// +// Floating point, integer, and Number values encode as JSON numbers. +// +// String values encode as Hjson strings (quoteless, multiline or +// JSON). +// +// Array and slice values encode as JSON arrays. +// +// Map values encode as JSON objects. The map's key type must be a +// string. The map keys are sorted and used as JSON object keys. +// +// Pointer values encode as the value pointed to. +// A nil pointer encodes as the null JSON value. +// +// Interface values encode as the value contained in the interface. +// A nil interface value encodes as the null JSON value. +// +// JSON cannot represent cyclic data structures and Marshal does not +// handle them. Passing cyclic structures to Marshal will result in +// an infinite recursion. +// +func MarshalWithOptions(v interface{}, options EncoderOptions) ([]byte, error) { + e := &hjsonEncoder{} + e.indent = 0 + e.Eol = options.Eol + e.BracesSameLine = options.BracesSameLine + e.QuoteAlways = options.QuoteAlways + e.IndentBy = options.IndentBy + + err := e.str(reflect.ValueOf(v), true, "", true) + if err != nil { + return nil, err + } + return e.Bytes(), nil +} diff --git a/vendor/gopkg.in/hjson/hjson-go.v3/parseNumber.go b/vendor/gopkg.in/hjson/hjson-go.v3/parseNumber.go new file mode 100644 index 00000000000..2e66f500160 --- /dev/null +++ b/vendor/gopkg.in/hjson/hjson-go.v3/parseNumber.go @@ -0,0 +1,108 @@ +package hjson + +import ( + "errors" + "math" + "strconv" +) + +type parseNumber struct { + data []byte + at int // The index of the current character + ch byte // The current character +} + +func (p *parseNumber) next() bool { + // get the next character. + len := len(p.data) + if p.at < len { + p.ch = p.data[p.at] + p.at++ + return true + } + if p.at == len { + p.at++ + p.ch = 0 + } + return false +} + +func (p *parseNumber) peek(offs int) byte { + pos := p.at + offs + if pos >= 0 && pos < len(p.data) { + return p.data[pos] + } + return 0 +} + +func startsWithNumber(text []byte) bool { + if _, err := tryParseNumber(text, true); err == nil { + return true + } + return false +} + +func tryParseNumber(text []byte, stopAtNext bool) (float64, error) { + // Parse a number value. + + p := parseNumber{text, 0, ' '} + leadingZeros := 0 + testLeading := true + p.next() + if p.ch == '-' { + p.next() + } + for p.ch >= '0' && p.ch <= '9' { + if testLeading { + if p.ch == '0' { + leadingZeros++ + } else { + testLeading = false + } + } + p.next() + } + if testLeading { + leadingZeros-- + } // single 0 is allowed + if p.ch == '.' { + for p.next() && p.ch >= '0' && p.ch <= '9' { + } + } + if p.ch == 'e' || p.ch == 'E' { + p.next() + if p.ch == '-' || p.ch == '+' { + p.next() + } + for p.ch >= '0' && p.ch <= '9' { + p.next() + } + } + + end := p.at + + // skip white/to (newline) + for p.ch > 0 && p.ch <= ' ' { + p.next() + } + + if stopAtNext { + // end scan if we find a punctuator character like ,}] or a comment + if p.ch == ',' || p.ch == '}' || p.ch == ']' || + p.ch == '#' || p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') { + p.ch = 0 + } + } + + if p.ch > 0 || leadingZeros != 0 { + return 0, errors.New("Invalid number") + } + number, err := strconv.ParseFloat(string(p.data[0:end-1]), 64) + if err != nil { + return 0, err + } + if math.IsInf(number, 0) || math.IsNaN(number) { + return 0, errors.New("Invalid number") + } + return number, nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 25c81f3df6f..a2b41f113fc 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3285,52 +3285,76 @@ "versionExact": "v0.0.7" }, { - "checksumSHA1": "b91TSC0atoGVSMZdKuWTYsMOGiM=", + "checksumSHA1": "a1x0prr3ix+QFOdJpNfM3Q4G6Es=", "path": "github.com/elastic/go-ucfg", - "revision": "0539807037ce820e147797f051ff32b05f4f9288", - "revisionTime": "2019-01-28T11:18:48Z", - "version": "v0.7.0", - "versionExact": "v0.7.0" + "revision": "093a6898c440d2e5e93abf09850068c60428b2cd", + "revisionTime": "2020-01-31T13:55:22Z", + "version": "v0.8.1", + "versionExact": "v0.8.1" }, { "checksumSHA1": "X+R/CD8SokJrmlxFTx2nSevRDhQ=", "path": "github.com/elastic/go-ucfg/cfgutil", - "revision": "0539807037ce820e147797f051ff32b05f4f9288", - "revisionTime": "2019-01-28T11:18:48Z", - "version": "v0.7.0", - "versionExact": "v0.7.0" + "revision": "093a6898c440d2e5e93abf09850068c60428b2cd", + "revisionTime": "2020-01-31T13:55:22Z", + "version": "v0.8.1", + "versionExact": "v0.8.1" }, { - "checksumSHA1": "/pq8HNEdzHmky9S9HSo1WofXQ4Y=", + "checksumSHA1": "B58nBBiUIstpkXfr9yaYN9GzdLE=", + "path": "github.com/elastic/go-ucfg/diff", + "revision": "093a6898c440d2e5e93abf09850068c60428b2cd", + "revisionTime": "2020-01-31T13:55:22Z", + "version": "v0.8.1", + "versionExact": "v0.8.1" + }, + { + "checksumSHA1": "E6k6DWkpI+LOKDIFRqRmNH9GORc=", "path": "github.com/elastic/go-ucfg/flag", - "revision": "0539807037ce820e147797f051ff32b05f4f9288", - "revisionTime": "2019-01-28T11:18:48Z", - "version": "v0.7.0", - "versionExact": "v0.7.0" + "revision": "093a6898c440d2e5e93abf09850068c60428b2cd", + "revisionTime": "2020-01-31T13:55:22Z", + "version": "v0.8.1", + "versionExact": "v0.8.1" + }, + { + "checksumSHA1": "NhiQQjYMs/ViCbmEq9tll2uCaYo=", + "path": "github.com/elastic/go-ucfg/hjson", + "revision": "093a6898c440d2e5e93abf09850068c60428b2cd", + "revisionTime": "2020-01-31T13:55:22Z", + "version": "v0.8.1", + "versionExact": "v0.8.1" }, { "checksumSHA1": "esXpiQlEvTOUwsE0nNesso8albo=", "path": "github.com/elastic/go-ucfg/internal/parse", "revision": "0539807037ce820e147797f051ff32b05f4f9288", "revisionTime": "2019-01-28T11:18:48Z", - "version": "v0.7.0", - "versionExact": "v0.7.0" + "version": "v0.8.1", + "versionExact": "v0.8.1" }, { "checksumSHA1": "cfMNsyQm0gZOV0hRJrBSdKDQSBo=", "path": "github.com/elastic/go-ucfg/json", - "revision": "0539807037ce820e147797f051ff32b05f4f9288", - "revisionTime": "2019-01-28T11:18:48Z", - "version": "v0.7.0", - "versionExact": "v0.7.0" + "revision": "093a6898c440d2e5e93abf09850068c60428b2cd", + "revisionTime": "2020-01-31T13:55:22Z", + "version": "v0.8.1", + "versionExact": "v0.8.1" }, { - "checksumSHA1": "PJCBACDGPhnRAEqjGPMPCMjbj4o=", + "checksumSHA1": "ZISq+zzSb0OLzvwLlf1ObdgnFmM=", + "path": "github.com/elastic/go-ucfg/parse", + "revision": "093a6898c440d2e5e93abf09850068c60428b2cd", + "revisionTime": "2020-01-31T13:55:22Z", + "version": "v0.8.1", + "versionExact": "v0.8.1" + }, + { + "checksumSHA1": "cnJVnptTvXNLzxVhd266k19/pQg=", "path": "github.com/elastic/go-ucfg/yaml", - "revision": "0539807037ce820e147797f051ff32b05f4f9288", - "revisionTime": "2019-01-28T11:18:48Z", - "version": "v0.7.0", - "versionExact": "v0.7.0" + "revision": "093a6898c440d2e5e93abf09850068c60428b2cd", + "revisionTime": "2020-01-31T13:55:22Z", + "version": "v0.8.1", + "versionExact": "v0.8.1" }, { "checksumSHA1": "iI1JCWsrAPpoKcEo/i6G3lRLIVs=", @@ -6438,6 +6462,12 @@ "version": "v1.25.1", "versionExact": "v1.25.1" }, + { + "checksumSHA1": "cEgwzgGEO4v4ARZB2EjDd2VjB64=", + "path": "gopkg.in/hjson/hjson-go.v3", + "revision": "8f44e1182a33737b57cb6855fe656ece998e9b3c", + "revisionTime": "2019-11-06T16:53:36Z" + }, { "checksumSHA1": "6f8MEU31llHM1sLM/GGH4/Qxu0A=", "path": "gopkg.in/inf.v0", diff --git a/x-pack/filebeat/input/httpjson/config.go b/x-pack/filebeat/input/httpjson/config.go index 7f2d9b3c530..a1baece249f 100644 --- a/x-pack/filebeat/input/httpjson/config.go +++ b/x-pack/filebeat/input/httpjson/config.go @@ -21,7 +21,7 @@ type config struct { HTTPHeaders common.MapStr `config:"http_headers"` HTTPMethod string `config:"http_method" validate:"required"` HTTPRequestBody common.MapStr `config:"http_request_body"` - Interval time.Duration `config:"interval" validate:"required"` + Interval time.Duration `config:"interval"` JSONObjects string `config:"json_objects_array"` Pagination *Pagination `config:"pagination"` TLS *tlscommon.Config `config:"ssl"`