Skip to content

Commit

Permalink
allow/merge fields.yml overrides (elastic#9188)
Browse files Browse the repository at this point in the history
  • Loading branch information
graphaelli authored Nov 27, 2018
1 parent ab67b31 commit 1c1b7d7
Show file tree
Hide file tree
Showing 15 changed files with 131 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-developer.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ The list below covers the major changes between 6.3.0 and master only.
- Add docker image building to `mage.Package`. {pull}8898[8898]
- Simplified exporting of dashboards. {pull}7730[7730]
- Update Beats to use go 1.11.2 {pull}8746[8746]
- Allow/Merge fields.yml overrides {pull}9188[9188]
2 changes: 1 addition & 1 deletion auditbeat/include/fields.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dev-tools/cmd/kibana_index_pattern/kibana_index_pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func main() {

file, err := indexPattern.Generate()
if err != nil {
log.Fatal(err)
log.Fatalf("ERROR: %s", err)
}

// Log output file location.
Expand Down
2 changes: 1 addition & 1 deletion filebeat/include/fields.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion heartbeat/include/fields.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion journalbeat/include/fields.go

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion libbeat/common/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ type Field struct {
UrlTemplate []VersionizedString `config:"url_template"`
OpenLinkInCurrentTab *bool `config:"open_link_in_current_tab"`

Path string
Overwrite bool `config:"overwrite"`
Path string
}

type VersionizedString struct {
Expand Down
15 changes: 8 additions & 7 deletions libbeat/generator/fields/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,13 @@ type YmlFile struct {
}

func collectCommonFiles(esBeatsPath, beatPath string, fieldFiles []*YmlFile) ([]*YmlFile, error) {
commonFields := []string{
// Fields for custom beats
filepath.Join(beatPath, "_meta/fields.yml"),
filepath.Join(beatPath, "_meta/fields.common.yml"),
}

var commonFields []string
var libbeatFieldFiles []*YmlFile
var err error
if !isLibbeat(beatPath) {
commonFields = append(commonFields,
filepath.Join(esBeatsPath, "libbeat/_meta/fields.common.yml"),
filepath.Join(esBeatsPath, "libbeat/_meta/fields.ecs.yml"),
filepath.Join(esBeatsPath, "libbeat/_meta/fields.common.yml"),
)

libbeatModulesPath := filepath.Join(esBeatsPath, "libbeat/processors")
Expand All @@ -53,6 +48,12 @@ func collectCommonFiles(esBeatsPath, beatPath string, fieldFiles []*YmlFile) ([]
}
}

// Fields for custom beats last, to enable overriding more generically defined fields
commonFields = append(commonFields,
filepath.Join(beatPath, "_meta/fields.common.yml"),
filepath.Join(beatPath, "_meta/fields.yml"),
)

var files []*YmlFile
for _, cf := range commonFields {
_, err := os.Stat(cf)
Expand Down
34 changes: 25 additions & 9 deletions libbeat/kibana/fields_transformer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type fieldsTransformer struct {
transformedFields []common.MapStr
transformedFieldFormatMap common.MapStr
version *common.Version
keys common.MapStr
keys map[string]int
}

func newFieldsTransformer(version *common.Version, fields common.Fields) (*fieldsTransformer, error) {
Expand All @@ -41,7 +41,7 @@ func newFieldsTransformer(version *common.Version, fields common.Fields) (*field
version: version,
transformedFields: []common.MapStr{},
transformedFieldFormatMap: common.MapStr{},
keys: common.MapStr{},
keys: map[string]int{},
}, nil
}

Expand Down Expand Up @@ -79,17 +79,11 @@ func (t *fieldsTransformer) transformFields(commonFields common.Fields, path str
f.Path = path + "." + f.Name
}

if t.keys[f.Path] != nil {
msg := fmt.Sprintf("ERROR: Field <%s> is duplicated. Please update and try again.\n", f.Path)
panic(errors.New(msg))
}

if f.Type == "group" {
if f.Enabled == nil || *f.Enabled {
t.transformFields(f.Fields, f.Path)
}
} else {
t.keys[f.Path] = true
t.add(f)

if f.MultiFields != nil {
Expand All @@ -104,13 +98,35 @@ func (t *fieldsTransformer) transformFields(commonFields common.Fields, path str
}
}

func (t *fieldsTransformer) update(target *common.MapStr, override common.Field) error {
field, _ := transformField(t.version, override)
if override.Type == "" || (*target)["type"] == field["type"] {
target.Update(field)
if !override.Overwrite {
// compatible duplication
return fmt.Errorf("field <%s> is duplicated, remove it or set 'overwrite: true'", override.Path)
}
return nil
}
// incompatible duplication
return fmt.Errorf("field <%s> is duplicated", override.Path)
}

func (t *fieldsTransformer) add(f common.Field) {
if idx := t.keys[f.Path]; idx > 0 {
target := &t.transformedFields[idx-1] // 1-indexed
if err := t.update(target, f); err != nil {
panic(err)
}
return
}

field, fieldFormat := transformField(t.version, f)
t.transformedFields = append(t.transformedFields, field)
t.keys[f.Path] = len(t.transformedFields) // 1-index
if fieldFormat != nil {
t.transformedFieldFormatMap[field["name"].(string)] = fieldFormat
}

}

func transformField(version *common.Version, f common.Field) (common.MapStr, common.MapStr) {
Expand Down
72 changes: 67 additions & 5 deletions libbeat/kibana/fields_transformer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elastic/beats/libbeat/common"
)
Expand Down Expand Up @@ -97,14 +98,75 @@ func TestMissingVersion(t *testing.T) {
}

func TestDuplicateField(t *testing.T) {
testCases := []struct {
commonFields []common.Field
}{
// type change
{commonFields: []common.Field{
{Name: "context", Path: "something"},
{Name: "context", Path: "something", Type: "date"},
}},
// missing overwrite
{commonFields: []common.Field{
{Name: "context", Path: "something"},
{Name: "context", Path: "something"},
}},
// missing overwrite in source
{commonFields: []common.Field{
{Name: "context", Path: "something", Overwrite: true},
{Name: "context", Path: "something"},
}},
}
for _, testCase := range testCases {
trans, err := newFieldsTransformer(version, testCase.commonFields)
require.NoError(t, err)
_, err = trans.transform()
assert.Error(t, err)
}
}

func TestValidDuplicateField(t *testing.T) {
commonFields := common.Fields{
common.Field{Name: "context", Path: "something"},
common.Field{Name: "context", Path: "something", Type: "keyword"},
common.Field{Name: "context", Path: "something", Type: "keyword", Description: "original description"},
common.Field{Name: "context", Path: "something", Overwrite: true, Description: "updated description",
Aggregatable: &falsy,
Analyzed: &truthy,
Count: 2,
DocValues: &falsy,
Index: &falsy,
Searchable: &falsy,
},
common.Field{
Name: "context",
Type: "group",
Fields: common.Fields{
common.Field{Name: "another", Type: "date"},
},
},
common.Field{
Name: "context",
Type: "group",
Fields: common.Fields{
common.Field{Name: "another", Overwrite: true},
},
},
}
trans, err := newFieldsTransformer(version, commonFields)
assert.NoError(t, err)
_, err = trans.transform()
assert.Error(t, err)
require.NoError(t, err)
transformed, err := trans.transform()
require.NoError(t, err)
out := transformed["fields"].([]common.MapStr)[0]
assert.Equal(t, out, common.MapStr{
"aggregatable": false,
"analyzed": true,
"count": 2,
"doc_values": false,
"indexed": false,
"name": "context",
"scripted": false,
"searchable": false,
"type": "string",
})
}

func TestInvalidVersion(t *testing.T) {
Expand Down
21 changes: 19 additions & 2 deletions libbeat/scripts/generate_fields_docs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import yaml
import os
import argparse
from collections import OrderedDict
import os

import yaml


def document_fields(output, section, sections, path):
Expand Down Expand Up @@ -102,6 +104,21 @@ def fields_to_asciidoc(input, output, beat):
print("fields.yml file is empty. fields.asciidoc cannot be generated.")
return

# deduplicate fields, last one wins
for section in docs:
if not section.get("fields"):
continue
fields = OrderedDict()
for field in section["fields"]:
name = field["name"]
if name in fields:
assert field["type"] == fields[name]["type"], 'field "{}" redefined with different type "{}"'.format(
name, field["type"])
fields[name].update(field)
else:
fields[name] = field
section["fields"] = list(fields.values())

# Create sections from available fields
sections = {}
for v in docs:
Expand Down
2 changes: 1 addition & 1 deletion metricbeat/include/fields/fields.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packetbeat/include/fields.go

Large diffs are not rendered by default.

Loading

0 comments on commit 1c1b7d7

Please sign in to comment.