From 07d0c8c1b4fe16da1bdf474ae3cf6eb8b1cb2b89 Mon Sep 17 00:00:00 2001 From: Adrian Serrano Date: Thu, 23 Apr 2020 10:35:21 +0200 Subject: [PATCH] Fix setup.dashboards.index not working (#17749) Due to type casting nightmare, the setting of `setup.dashboards.index` configuration option to replace the index name in use for dashboards and index pattern wasn't being honored. Fixes #14019 --- CHANGELOG.next.asciidoc | 1 + libbeat/dashboards/kibana_loader.go | 14 ++- libbeat/dashboards/modify_json.go | 52 +++++++--- libbeat/dashboards/modify_json_test.go | 134 +++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 71aae915595..8076c533689 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -76,6 +76,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix building on FreeBSD by removing build flags from `add_cloudfoundry_metadata` processor. {pull}17486[17486] - Do not rotate log files on startup when interval is configured and rotateonstartup is disabled. {pull}17613[17613] - Fix goroutine leak and Elasticsearch output file descriptor leak when output reloading is in use. {issue}10491[10491] {pull}17381[17381] +- Fix `setup.dashboards.index` setting not working. {pull}17749[17749] *Auditbeat* diff --git a/libbeat/dashboards/kibana_loader.go b/libbeat/dashboards/kibana_loader.go index 93dd0e5dc0e..1733f94750c 100644 --- a/libbeat/dashboards/kibana_loader.go +++ b/libbeat/dashboards/kibana_loader.go @@ -25,6 +25,9 @@ import ( "net/url" "time" + "github.com/joeshaw/multierror" + "github.com/pkg/errors" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/kibana" "github.com/elastic/beats/v7/libbeat/logp" @@ -101,11 +104,18 @@ func (loader KibanaLoader) ImportIndexFile(file string) error { // ImportIndex imports the passed index pattern to Kibana func (loader KibanaLoader) ImportIndex(pattern common.MapStr) error { + var errs multierror.Errors + params := url.Values{} params.Set("force", "true") //overwrite the existing dashboards - indexContent := ReplaceIndexInIndexPattern(loader.config.Index, pattern) - return loader.client.ImportJSON(importAPI, params, indexContent) + if err := ReplaceIndexInIndexPattern(loader.config.Index, pattern); err != nil { + errs = append(errs, errors.Wrapf(err, "error setting index '%s' in index pattern", loader.config.Index)) + } + if err := loader.client.ImportJSON(importAPI, params, pattern); err != nil { + errs = append(errs, errors.Wrap(err, "error loading index pattern")) + } + return errs.Err() } // ImportDashboard imports the dashboard file diff --git a/libbeat/dashboards/modify_json.go b/libbeat/dashboards/modify_json.go index c8b1c79da6b..2e0c48e38b0 100644 --- a/libbeat/dashboards/modify_json.go +++ b/libbeat/dashboards/modify_json.go @@ -22,6 +22,8 @@ import ( "encoding/json" "fmt" + "github.com/pkg/errors" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/logp" ) @@ -41,32 +43,50 @@ type JSONFormat struct { Objects []JSONObject `json:"objects"` } -func ReplaceIndexInIndexPattern(index string, content common.MapStr) common.MapStr { +func ReplaceIndexInIndexPattern(index string, content common.MapStr) (err error) { if index == "" { - return content + return nil } - objects, ok := content["objects"].([]interface{}) + list, ok := content["objects"] if !ok { - return content + return errors.New("empty index pattern") } - // change index pattern name - for i, object := range objects { - objectMap, ok := object.(map[string]interface{}) - if !ok { - continue + updateObject := func(obj common.MapStr) { + // This uses Put instead of DeepUpdate to avoid modifying types for + // inner objects. (DeepUpdate will replace maps with MapStr). + obj.Put("id", index) + // Only overwrite title if it exists. + if _, err := obj.GetValue("attributes.title"); err == nil { + obj.Put("attributes.title", index) } + } - objectMap["id"] = index - if attributes, ok := objectMap["attributes"].(map[string]interface{}); ok { - attributes["title"] = index + switch v := list.(type) { + case []interface{}: + for _, objIf := range v { + switch obj := objIf.(type) { + case common.MapStr: + updateObject(obj) + case map[string]interface{}: + updateObject(obj) + default: + return errors.Errorf("index pattern object has unexpected type %T", v) + } } - objects[i] = objectMap + case []map[string]interface{}: + for _, obj := range v { + updateObject(obj) + } + case []common.MapStr: + for _, obj := range v { + updateObject(obj) + } + default: + return errors.Errorf("index pattern objects have unexpected type %T", v) } - content["objects"] = objects - - return content + return nil } func replaceIndexInSearchObject(index string, savedObject string) (string, error) { diff --git a/libbeat/dashboards/modify_json_test.go b/libbeat/dashboards/modify_json_test.go index 08fedac4df3..a7424414b53 100644 --- a/libbeat/dashboards/modify_json_test.go +++ b/libbeat/dashboards/modify_json_test.go @@ -111,3 +111,137 @@ func TestReplaceIndexInDashboardObject(t *testing.T) { assert.Equal(t, test.expected, result) } } + +func TestReplaceIndexInIndexPattern(t *testing.T) { + // Test that replacing of index name in index pattern works no matter + // what the inner types are (MapStr, map[string]interface{} or interface{}). + // Also ensures that the inner types are not modified after replacement. + tests := []struct { + title string + input common.MapStr + index string + expected common.MapStr + }{ + { + title: "Replace in []interface(map).map", + input: common.MapStr{"objects": []interface{}{map[string]interface{}{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": map[string]interface{}{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + }}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []interface{}{map[string]interface{}{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": map[string]interface{}{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + }}}}, + }, + { + title: "Replace in []interface(map).mapstr", + input: common.MapStr{"objects": []interface{}{map[string]interface{}{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + }}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []interface{}{map[string]interface{}{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + }}}}, + }, + { + title: "Replace in []map.mapstr", + input: common.MapStr{"objects": []map[string]interface{}{{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + }}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []map[string]interface{}{{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + }}}}, + }, + { + title: "Replace in []mapstr.mapstr", + input: common.MapStr{"objects": []common.MapStr{{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + }}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []common.MapStr{{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": common.MapStr{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + }}}}, + }, + { + title: "Replace in []mapstr.interface(mapstr)", + input: common.MapStr{"objects": []common.MapStr{{ + "id": "phonybeat-*", + "type": "index-pattern", + "attributes": interface{}(common.MapStr{ + "title": "phonybeat-*", + "timeFieldName": "@timestamp", + })}}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []common.MapStr{{ + "id": "otherindex-*", + "type": "index-pattern", + "attributes": interface{}(common.MapStr{ + "title": "otherindex-*", + "timeFieldName": "@timestamp", + })}}}, + }, + { + title: "Do not create missing attributes", + input: common.MapStr{"objects": []common.MapStr{{ + "id": "phonybeat-*", + "type": "index-pattern", + }}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []common.MapStr{{ + "id": "otherindex-*", + "type": "index-pattern", + }}}, + }, + { + title: "Create missing id", + input: common.MapStr{"objects": []common.MapStr{{ + "type": "index-pattern", + }}}, + index: "otherindex-*", + expected: common.MapStr{"objects": []common.MapStr{{ + "id": "otherindex-*", + "type": "index-pattern", + }}}, + }, + } + + for _, test := range tests { + t.Run(test.title, func(t *testing.T) { + err := ReplaceIndexInIndexPattern(test.index, test.input) + assert.NoError(t, err) + assert.Equal(t, test.expected, test.input) + }) + } +}