Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dashboard exporting saves to file in case of modules #9076

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions filebeat/scripts/generator/dashboards.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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 generator

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/paths"
)

const (
dashboardEntry = `- id: %s
file: %s
`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the module.yml this looks like generic YAML files, minus the extra space between lines. Is there a specific reason why we don't use the YAML package and structs to serialize / unserialize content.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, nothing. I planned to use the yaml package to read and write module.yml. I was just curious if you like the direction of the PR. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kvch I like it :)

)

func AddModuleDashboard(beatName, module, kibanaVersion, dashboardID string, dashboard common.MapStr, suffix string) error {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported function AddModuleDashboard should have comment or be unexported

version, err := common.NewVersion(kibanaVersion)
if err != nil {
return err
}
if version.Major < 6 {
return fmt.Errorf("saving exported dashboards is not available for Kibana version '%s'", version.String())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to report that you need at least kibana 6.


modulePath := filepath.Join(paths.Resolve(paths.Home, "module"), module)
stat, err := os.Stat(modulePath)
if err != nil || !stat.IsDir() {
return fmt.Errorf("no such module: %s\n", modulePath)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error strings should not be capitalized or end with punctuation or a newline

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to report a more accurate error, I think you actually want to use https://golang.org/pkg/os/#IsExist instead of IsDir() or https://golang.org/pkg/os/#IsNotExist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? If there is no error it means the file exists and I have the proper permission to access it. I also need to check if that module is a folder. If it is not a folder, I cannot create more folders under it and cannot saves files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the same check as it is done in case of the ModuleRegistry: https://github.com/elastic/beats/blob/master/filebeat/fileset/modules.go#L116

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough


dashboardFile := strings.Title(beatName) + "-" + module + "-" + suffix + ".json"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 'title'? Given default case insensitivity Windows/macOS filesystems can be a pain with in comparison to Linux.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this format is the name convention for Filebeat module dashboards, as 99% percent of them are named Filebeat-{modulename}-{suffix}.json.


err = saveDashboardToFile(version, dashboard, dashboardFile, modulePath)
if err != nil {
return fmt.Errorf("cannot save dashboard to file: %+v", err)
}

return addDashboardToModuleYML(dashboardID, dashboardFile, modulePath)
}

func saveDashboardToFile(version *common.Version, dashboard common.MapStr, dashboardFile, modulePath string) error {
dashboardsPath := "_meta/kibana/" + strconv.Itoa(version.Major) + "/dashboard"
err := CreateDirectories(modulePath, dashboardsPath)
if err != nil {
return err
}

dashboardPath := filepath.Join(modulePath, dashboardsPath, dashboardFile)
bytes, err := json.Marshal(dashboard)
if err != nil {
return err
}

return ioutil.WriteFile(dashboardPath, bytes, 0644)
}

func addDashboardToModuleYML(dashboardID, dashboardFile, modulePath string) error {
content := fmt.Sprintf(dashboardEntry, dashboardID, dashboardFile)

f, err := os.OpenFile(filepath.Join(modulePath, "module.yml"), os.O_WRONLY|os.O_APPEND, 0644)
if err == nil {
_, err = f.Write([]byte(content))
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better use ioutil.WriteFile. This also checks the amount of bytes written and errors if write was incomplete.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh... it's an append. In this case check the amount of bytes written. If some bytes are written, but write is incomplete then the file is broken.


return err
}
2 changes: 1 addition & 1 deletion filebeat/scripts/generator/fileset/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func generateFileset(module, fileset, modulesPath, beatsPath string) error {
return fmt.Errorf("fileset already exists: %s", fileset)
}

err := generator.CreateDirectories(filesetPath, []string{"", "_meta", "test", "config", "ingest"})
err := generator.CreateDirectories(filesetPath, "", "_meta", "test", "config", "ingest")
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion filebeat/scripts/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func DirExists(dir string) bool {
}

// CreateDirectories create directories in baseDir
func CreateDirectories(baseDir string, directories []string) error {
func CreateDirectories(baseDir string, directories ...string) error {
for _, d := range directories {
p := path.Join(baseDir, d)
err := os.MkdirAll(p, 0750)
Expand Down
14 changes: 7 additions & 7 deletions filebeat/scripts/generator/module/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ func generateModule(module, modulesPath, beatsPath string) error {
return fmt.Errorf("module already exists: %s", module)
}

err := generator.CreateDirectories(modulePath, []string{path.Join("_meta", "kibana", "6")})
if err != nil {
return err
}

replace := map[string]string{"module": module}
templatesPath := path.Join(beatsPath, "scripts", "module")
filesToCopy := []string{path.Join("_meta", "fields.yml"), path.Join("_meta", "docs.asciidoc"), path.Join("_meta", "config.yml"), path.Join("module.yml")}
generator.CopyTemplates(templatesPath, modulePath, filesToCopy, replace)
filesToCopy := []string{
path.Join("_meta", "fields.yml"),
path.Join("_meta", "docs.asciidoc"),
path.Join("_meta", "config.yml"),
"module.yml",
}
err := generator.CopyTemplates(templatesPath, modulePath, filesToCopy, replace)
if err != nil {
return err
}
Expand Down
15 changes: 15 additions & 0 deletions libbeat/cmd/export/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/spf13/cobra"

"github.com/elastic/beats/filebeat/scripts/generator"
"github.com/elastic/beats/libbeat/cmd/instance"
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/kibana"
Expand All @@ -35,6 +36,8 @@ func GenDashboardCmd(name, idxPrefix, beatVersion string) *cobra.Command {
Short: "Export defined dashboard to stdout",
Run: func(cmd *cobra.Command, args []string) {
dashboard, _ := cmd.Flags().GetString("id")
module, _ := cmd.Flags().GetString("module")
suffix, _ := cmd.Flags().GetString("suffix")

b, err := instance.NewBeat(name, idxPrefix, beatVersion)
if err != nil {
Expand Down Expand Up @@ -63,11 +66,23 @@ func GenDashboardCmd(name, idxPrefix, beatVersion string) *cobra.Command {
fmt.Fprintf(os.Stderr, "Error getting dashboard: %+v\n", err)
os.Exit(1)
}

if module != "" {
err := generator.AddModuleDashboard(name, module, client.GetVersion(), dashboard, result, suffix)
if err != nil {
fmt.Fprintf(os.Stderr, "Error adding dashboard to module '%s': %+v\n", module, err)
os.Exit(2)
}
return
}

fmt.Println(result.StringToPrint())
},
}

genTemplateConfigCmd.Flags().String("id", "", "Dashboard id")
genTemplateConfigCmd.Flags().String("module", "", "Name of the module to save for")
genTemplateConfigCmd.Flags().String("suffix", "overview", "Suffix of the dashboard name")

return genTemplateConfigCmd
}