-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Add the option to generate the template into a file #4323
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -469,8 +469,30 @@ func (b *Beat) registerTemplateLoading() error { | |
} | ||
} | ||
|
||
esConfig := b.Config.Output["elasticsearch"] | ||
// Check if outputting to file is enabled, and output to file if it is | ||
if b.Config.Template != nil && b.Config.Template.Enabled() { | ||
var cfg template.TemplateConfig | ||
err := b.Config.Template.Unpack(&cfg) | ||
if err != nil { | ||
return fmt.Errorf("unpacking template config fails: %v", err) | ||
} | ||
if len(cfg.OutputToFile.Path) > 0 { | ||
// output to file is enabled | ||
loader, err := template.NewLoader(b.Config.Template, nil, b.Info) | ||
if err != nil { | ||
return fmt.Errorf("Error creating Elasticsearch template loader: %v", err) | ||
} | ||
err = loader.Generate() | ||
if err != nil { | ||
return fmt.Errorf("Error generating template: %v", err) | ||
} | ||
|
||
// XXX: Should we kill the Beat here or just continue? | ||
return fmt.Errorf("Stopping after successfully writing the template to the file.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [golint] reported by reviewdog 🐶 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case, dear reviewdog, we know that the error is final, so the punctuation looks better to the user. |
||
} | ||
} | ||
|
||
esConfig := b.Config.Output["elasticsearch"] | ||
// Loads template by default if esOutput is enabled | ||
if (b.Config.Template == nil && esConfig.Enabled()) || (b.Config.Template != nil && b.Config.Template.Enabled()) { | ||
if esConfig == nil || !esConfig.Enabled() { | ||
|
@@ -487,7 +509,7 @@ func (b *Beat) registerTemplateLoading() error { | |
|
||
loader, err := template.NewLoader(b.Config.Template, esClient, b.Info) | ||
if err != nil { | ||
return fmt.Errorf("Error creating Elasticsearch template: %v", err) | ||
return fmt.Errorf("Error creating Elasticsearch template loader: %v", err) | ||
} | ||
|
||
err = loader.Load() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,15 @@ type TemplateConfig struct { | |
Name string `config:"name"` | ||
Fields string `config:"fields"` | ||
Overwrite bool `config:"overwrite"` | ||
OutputToFile string `config:"output_to_file"` | ||
Settings templateSettings `config:"settings"` | ||
OutputToFile OutputToFile `config:"output_to_file"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not too much of a fan of the config name, but well :-) |
||
} | ||
|
||
// OutputToFile contains the configuration options for generating | ||
// and writing the template into a file. | ||
type OutputToFile struct { | ||
Path string `config:"path"` | ||
Version string `config:"version"` | ||
} | ||
|
||
type templateSettings struct { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
package template | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
|
||
"github.com/elastic/beats/libbeat/common" | ||
"github.com/elastic/beats/libbeat/logp" | ||
|
@@ -38,7 +40,7 @@ func NewLoader(cfg *common.Config, client ESClient, beatInfo common.BeatInfo) (* | |
}, nil | ||
} | ||
|
||
// loadTemplate checks if the index mapping template should be loaded | ||
// Load checks if the index mapping template should be loaded | ||
// In case the template is not already loaded or overwriting is enabled, the | ||
// template is written to index | ||
func (l *Loader) Load() error { | ||
|
@@ -80,6 +82,43 @@ func (l *Loader) Load() error { | |
return nil | ||
} | ||
|
||
// Generate generates the template and writes it to a file based on the configuration | ||
// from `output_to_file`. | ||
func (l *Loader) Generate() error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could probably now use this function also in https://github.com/elastic/beats/blob/master/dev-tools/cmd/index_template/index_template.go There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I didn't realize we have that utility already. |
||
if l.config.OutputToFile.Version == "" { | ||
l.config.OutputToFile.Version = l.beatInfo.Version | ||
} | ||
|
||
if l.config.Name == "" { | ||
l.config.Name = l.beatInfo.Beat | ||
} | ||
|
||
tmpl, err := New(l.beatInfo.Version, l.config.OutputToFile.Version, l.config.Name, l.config.Settings) | ||
if err != nil { | ||
return fmt.Errorf("error creating template instance: %v", err) | ||
} | ||
|
||
fieldsPath := paths.Resolve(paths.Config, l.config.Fields) | ||
|
||
output, err := tmpl.Load(fieldsPath) | ||
if err != nil { | ||
return fmt.Errorf("error creating template from file %s: %v", fieldsPath, err) | ||
} | ||
|
||
jsonBytes, err := json.MarshalIndent(output, "", " ") | ||
if err != nil { | ||
return fmt.Errorf("error marshaling template: %v", err) | ||
} | ||
|
||
err = ioutil.WriteFile(l.config.OutputToFile.Path, jsonBytes, 0644) | ||
if err != nil { | ||
return fmt.Errorf("error writing to file %s: %v", l.config.OutputToFile.Path, err) | ||
} | ||
|
||
logp.Info("Template for Elasticsearch %s written to: %s", l.config.OutputToFile.Version, l.config.OutputToFile.Path) | ||
return nil | ||
} | ||
|
||
// LoadTemplate loads a template into Elasticsearch overwriting the existing | ||
// template if it exists. If you wish to not overwrite an existing template | ||
// then use CheckTemplate prior to calling this method. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// +build !integration | ||
|
||
package template | ||
|
||
import ( | ||
"encoding/json" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/elastic/beats/libbeat/common" | ||
"github.com/elastic/beats/libbeat/version" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestGenerateTemplate(t *testing.T) { | ||
|
||
// Load template | ||
absPath, err := filepath.Abs("../") | ||
assert.NotNil(t, absPath) | ||
assert.Nil(t, err) | ||
|
||
beatInfo := common.BeatInfo{ | ||
Beat: "testbeat", | ||
Version: version.GetDefaultVersion(), | ||
} | ||
|
||
dir, err := ioutil.TempDir("", "test-template") | ||
assert.NoError(t, err) | ||
defer os.RemoveAll(dir) | ||
path := filepath.Join(dir, "template.json") | ||
|
||
config := newConfigFrom(t, TemplateConfig{ | ||
Enabled: true, | ||
Fields: absPath + "/fields.yml", | ||
OutputToFile: OutputToFile{ | ||
Path: path, | ||
}, | ||
}) | ||
|
||
loader, err := NewLoader(config, nil, beatInfo) | ||
assert.NoError(t, err) | ||
|
||
err = loader.Generate() | ||
assert.NoError(t, err) | ||
|
||
// Read it back to check it | ||
fp, err := os.Open(path) | ||
assert.NoError(t, err) | ||
jsonParser := json.NewDecoder(fp) | ||
var parsed common.MapStr | ||
err = jsonParser.Decode(&parsed) | ||
assert.NoError(t, err) | ||
|
||
val, err := parsed.GetValue("mappings._default_._meta.version") | ||
assert.NoError(t, err) | ||
assert.Equal(t, val.(string), version.GetDefaultVersion()) | ||
|
||
} | ||
|
||
func TestGenerateTemplateWithVersion(t *testing.T) { | ||
|
||
// Load template | ||
absPath, err := filepath.Abs("../") | ||
assert.NotNil(t, absPath) | ||
assert.Nil(t, err) | ||
|
||
beatInfo := common.BeatInfo{ | ||
Beat: "testbeat", | ||
Version: version.GetDefaultVersion(), | ||
} | ||
|
||
dir, err := ioutil.TempDir("", "test-template") | ||
assert.NoError(t, err) | ||
defer os.RemoveAll(dir) | ||
path := filepath.Join(dir, "template.json") | ||
|
||
config := newConfigFrom(t, TemplateConfig{ | ||
Enabled: true, | ||
Fields: absPath + "/fields.yml", | ||
OutputToFile: OutputToFile{ | ||
Path: path, | ||
Version: "2.4.0", | ||
}, | ||
}) | ||
|
||
loader, err := NewLoader(config, nil, beatInfo) | ||
assert.NoError(t, err) | ||
|
||
err = loader.Generate() | ||
assert.NoError(t, err) | ||
|
||
// Read it back to check it | ||
fp, err := os.Open(path) | ||
assert.NoError(t, err) | ||
jsonParser := json.NewDecoder(fp) | ||
var parsed common.MapStr | ||
err = jsonParser.Decode(&parsed) | ||
assert.NoError(t, err) | ||
|
||
// check a setting specific to that version | ||
val, err := parsed.GetValue("mappings._default_._all.norms.enabled") | ||
assert.NoError(t, err) | ||
assert.Equal(t, val.(bool), false) | ||
} | ||
|
||
func newConfigFrom(t *testing.T, from interface{}) *common.Config { | ||
cfg, err := common.NewConfigFrom(from) | ||
assert.NoError(t, err) | ||
return cfg | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,4 +9,11 @@ def setUpClass(self): | |
self.beat_name = "mockbeat" | ||
self.beat_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) | ||
self.test_binary = self.beat_path + "/libbeat.test" | ||
self.beats = [ | ||
"filebeat", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like a beats specifics dependency in libbeat. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, only needed for those skipped tets for now, I can delete them. |
||
"heartbeat", | ||
"metricbeat", | ||
"packetbeat", | ||
"winlogbeat" | ||
] | ||
super(BaseTest, self).setUpClass() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
import subprocess | ||
import unittest | ||
import re | ||
from nose.plugins.skip import Skip, SkipTest | ||
from nose.plugins.skip import SkipTest | ||
|
||
|
||
INTEGRATION_TESTS = os.environ.get('INTEGRATION_TESTS', False) | ||
|
@@ -20,9 +20,7 @@ def test_load_dashboard(self): | |
""" | ||
Test loading dashboards for all beats | ||
""" | ||
beats = ["metricbeat", "packetbeat", "filebeat", "winlogbeat"] | ||
|
||
for beat in beats: | ||
for beat in self.beats: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. be aware that these tests are currently skipped. |
||
command = "go run ../../../dev-tools/cmd/import_dashboards/import_dashboards.go -es http://" + \ | ||
self.get_elasticsearch_host() + " -dir ../../../" + beat + "/_meta/kibana" | ||
|
||
|
@@ -51,9 +49,7 @@ def test_export_dashboard(self): | |
# In addition, this test should not write to the beats directory but to a | ||
# temporary directory and check the files there. | ||
|
||
beats = ["metricbeat", "packetbeat", "filebeat", "winlogbeat"] | ||
|
||
for beat in beats: | ||
for beat in self.beats: | ||
if os.name == "nt": | ||
path = "..\..\..\\" + beat + "\etc\kibana" | ||
else: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from base import BaseTest | ||
|
||
import os | ||
import json | ||
|
||
|
||
class Test(BaseTest): | ||
|
||
def test_generate_templates(self): | ||
""" | ||
Generates templates from other Beats. | ||
""" | ||
self.render_config_template() | ||
|
||
output_json = os.path.join(self.working_dir, "template.json") | ||
fields_yml = "../../../../fields.yml" | ||
|
||
exit_code = self.run_beat(extra_args=[ | ||
"-E", "setup.template.output_to_file.path={}".format(output_json), | ||
"-E", "setup.template.fields={}".format(fields_yml)]) | ||
assert exit_code == 1 | ||
|
||
# check json file | ||
with open(output_json) as f: | ||
tmpl = json.load(f) | ||
assert "mappings" in tmpl | ||
|
||
def test_generate_templates_v5(self): | ||
""" | ||
Generates templates from other Beats. | ||
""" | ||
self.render_config_template() | ||
|
||
output_json = os.path.join(self.working_dir, "template-5x.json") | ||
fields_yml = "../../../../fields.yml" | ||
|
||
exit_code = self.run_beat(extra_args=[ | ||
"-E", "setup.template.output_to_file.path={}".format(output_json), | ||
"-E", "setup.template.output_to_file.version=5.0.0".format(output_json), | ||
"-E", "setup.template.fields={}".format(fields_yml)]) | ||
assert exit_code == 1 | ||
|
||
# check json file | ||
with open(output_json) as f: | ||
tmpl = json.load(f) | ||
assert "mappings" in tmpl | ||
assert tmpl["mappings"]["_default_"]["_all"]["norms"]["enabled"] is False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment confused me at first because the check for the file only happens later, but I see you meant it for the "complete" block