From ccd36fb30397d45b2dc73e117ff6b649905f4fee Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Mon, 4 Jun 2018 09:42:22 +0200 Subject: [PATCH] Add support to export dashboard to Beat (#7239) Currently to export a Dashboard from the Kibana exporter API it has to be done manually or the Beats repository must be used. To simplify the exporting of own dashboards the command `beat export dashboard --id="dashboard-id"` is added. This should allow all users to export their own dashboards. The output is written to stdout, so the expected usage is to pipe it into a file inside the `kibana/dashboards/6` directory. For the Kibana connection settings it uses the setting from `setup.kibana.*`. If none are set it uses the defaults. --- CHANGELOG.asciidoc | 1 + libbeat/cmd/export.go | 1 + libbeat/cmd/export/dashboard.go | 56 +++++++++++++++++++++++++++++++++ libbeat/kibana/client.go | 16 ++++++++++ libbeat/kibana/dashboard.go | 47 +++++++++++++++++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 libbeat/cmd/export/dashboard.go create mode 100644 libbeat/kibana/dashboard.go diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 809af7f17b73..b7c58bbc7aab 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -148,6 +148,7 @@ https://github.com/elastic/beats/compare/v6.2.3...master[Check the HEAD diff] - Add support for keyword multifields in field.yml. {pull}7131[7131] - Add dissect processor. {pull}6925[6925] - Add owner object info to Kubernetes metadata. {pull}7231[7231] +- Add beat export dashboard command. {pull}7239[7239] *Auditbeat* diff --git a/libbeat/cmd/export.go b/libbeat/cmd/export.go index 6f33e8ad6412..b4c62741e074 100644 --- a/libbeat/cmd/export.go +++ b/libbeat/cmd/export.go @@ -14,6 +14,7 @@ func genExportCmd(name, idxPrefix, beatVersion string) *cobra.Command { exportCmd.AddCommand(export.GenExportConfigCmd(name, idxPrefix, beatVersion)) exportCmd.AddCommand(export.GenTemplateConfigCmd(name, idxPrefix, beatVersion)) + exportCmd.AddCommand(export.GenDashboardCmd(name, idxPrefix, beatVersion)) return exportCmd } diff --git a/libbeat/cmd/export/dashboard.go b/libbeat/cmd/export/dashboard.go new file mode 100644 index 000000000000..d9905117b4b5 --- /dev/null +++ b/libbeat/cmd/export/dashboard.go @@ -0,0 +1,56 @@ +package export + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/elastic/beats/libbeat/cmd/instance" + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/kibana" +) + +// GenDashboardCmd is the command used to export a dashboard. +func GenDashboardCmd(name, idxPrefix, beatVersion string) *cobra.Command { + genTemplateConfigCmd := &cobra.Command{ + Use: "dashboard", + Short: "Export defined dashboard to stdout", + Run: func(cmd *cobra.Command, args []string) { + dashboard, _ := cmd.Flags().GetString("id") + + b, err := instance.NewBeat(name, idxPrefix, beatVersion) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating beat: %s\n", err) + os.Exit(1) + } + err = b.Init() + if err != nil { + fmt.Fprintf(os.Stderr, "Error initializing beat: %s\n", err) + os.Exit(1) + } + + // Use empty config to use default configs if not set + if b.Config.Kibana == nil { + b.Config.Kibana = common.NewConfig() + } + + client, err := kibana.NewKibanaClient(b.Config.Kibana) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating Kibana client: %+v\n", err) + os.Exit(1) + } + + result, err := client.GetDashboard(dashboard) + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting dashboard: %+v\n", err) + os.Exit(1) + } + fmt.Println(result.StringToPrint()) + }, + } + + genTemplateConfigCmd.Flags().String("id", "", "Dashboard id") + + return genTemplateConfigCmd +} diff --git a/libbeat/kibana/client.go b/libbeat/kibana/client.go index 45a991046f12..2ed12ce39b5a 100644 --- a/libbeat/kibana/client.go +++ b/libbeat/kibana/client.go @@ -246,6 +246,22 @@ func (client *Client) ImportJSON(url string, params url.Values, jsonBody map[str func (client *Client) Close() error { return nil } +// GetDashboard returns the dashboard with the given id with the index pattern removed +func (client *Client) GetDashboard(id string) (common.MapStr, error) { + params := url.Values{} + params.Add("dashboard", id) + _, response, err := client.Request("GET", "/api/kibana/dashboards/export", params, nil, nil) + if err != nil { + return nil, fmt.Errorf("error exporting dashboard: %+v", err) + } + + result, err := RemoveIndexPattern(response) + if err != nil { + return nil, fmt.Errorf("error removing index pattern: %+v", err) + } + return result, nil +} + // truncateString returns a truncated string if the length is greater than 250 // runes. If the string is truncated "... (truncated)" is appended. Newlines are // replaced by spaces in the returned string. diff --git a/libbeat/kibana/dashboard.go b/libbeat/kibana/dashboard.go new file mode 100644 index 000000000000..bb996b362d22 --- /dev/null +++ b/libbeat/kibana/dashboard.go @@ -0,0 +1,47 @@ +package kibana + +import ( + "encoding/json" + "fmt" + + "github.com/elastic/beats/libbeat/common" +) + +// RemoveIndexPattern removes the index pattern entry from a given dashboard export +func RemoveIndexPattern(data []byte) (common.MapStr, error) { + + var kbResult struct { + // Has to be defined as interface instead of Type directly as it has to be assigned again + // and otherwise would not contain the full content. + Objects []interface{} + } + + var result common.MapStr + // Full struct need to not loose any data + err := json.Unmarshal(data, &result) + if err != nil { + return nil, err + } + + // For easier handling, unmarshal into predefined struct + err = json.Unmarshal(data, &kbResult) + if err != nil { + return nil, err + } + + var objs []interface{} + + for _, obj := range kbResult.Objects { + t, ok := obj.(map[string]interface{})["type"].(string) + if !ok { + return nil, fmt.Errorf("type key not found or not string") + } + if t != "index-pattern" { + objs = append(objs, obj) + } + } + + result["objects"] = objs + + return result, nil +}