dev/import-beats/fields.go (221 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. package main import ( "fmt" "io/ioutil" "os" "path/filepath" "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" ) type fieldsContent struct { files map[string]fieldDefinitionArray } type fieldDefinition struct { Name string `yaml:"name,omitempty"` Key string `yaml:"key,omitempty"` Title string `yaml:"title,omitempty"` Group *int `yaml:"group,omitempty"` Level string `yaml:"level,omitempty"` Required *bool `yaml:"required,omitempty"` Type string `yaml:"type,omitempty"` Format string `yaml:"format,omitempty"` Description string `yaml:"description,omitempty"` Release string `yaml:"release,omitempty"` Alias string `yaml:"alias,omitempty"` Path string `yaml:"path,omitempty"` Footnote string `yaml:"footnote,omitempty"` // Example is not consistent in ECS schema (either single field or array) // Example string `yaml:"example,omitempty"` IgnoreAbove *int `yaml:"ignore_above,omitempty"` MultiFields []multiFieldDefinition `yaml:"multi_fields,omitempty"` Fields fieldDefinitionArray `yaml:"fields,omitempty"` Migration *bool `yaml:"migration,omitempty"` skipped bool } type fieldDefinitionArray []fieldDefinition func (fda fieldDefinitionArray) names() []string { var names []string for _, f := range fda { names = append(names, collectFieldNames("", f)...) } return names } func (fda fieldDefinitionArray) stripped() fieldDefinitionArray { var arr fieldDefinitionArray for _, f := range fda { stripped := f if f.Type == "group" { stripped.Description = "" } stripped.Fields = stripped.Fields.stripped() arr = append(arr, stripped) } return arr } func collectFieldNames(namePrefix string, f fieldDefinition) []string { if namePrefix != "" { namePrefix = namePrefix + "." + f.Name } else { namePrefix = f.Name } if len(f.Fields) == 0 { return []string{namePrefix} } var collected []string for _, child := range f.Fields { collected = append(collected, collectFieldNames(namePrefix, child)...) } return collected } type multiFieldDefinition struct { Name string `yaml:"name,omitempty"` Type string `yaml:"type,omitempty"` Norms *bool `yaml:"norms,omitempty"` DefaultField *bool `yaml:"default_field,omitempty"` } func loadEcsFields(ecsDir string) ([]fieldDefinition, error) { path := filepath.Join(ecsDir, "generated/beats/fields.ecs.yml") fs, err := loadFieldsFile(path) if err != nil { return nil, errors.Wrapf(err, "loading ECS fields file failed") } if len(fs) != 1 { return nil, errors.Wrapf(err, "expected single root field") } return fs[0].Fields, nil } func loadModuleFields(modulePath string) ([]fieldDefinition, string, error) { path := filepath.Join(modulePath, "_meta", "fields.yml") fs, err := loadFieldsFile(path) if err != nil { return nil, "", errors.Wrapf(err, "loading module fields file failed") } if len(fs) != 1 { return nil, "", errors.Wrapf(err, "expected single root field") } title := fs[0].Title var unwrapped []fieldDefinition unwrapped = append(unwrapped, fs[0].Fields...) fieldsEpr := filepath.Join(modulePath, "_meta", "fields.epr.yml") efs, err := loadFieldsFile(fieldsEpr) if err != nil { return nil, "", errors.Wrapf(err, "loading fields.epr.yml file failed") } unwrapped = append(unwrapped, efs...) return unwrapped, title, nil } func loadDataStreamFields(modulePath, moduleName, dataStreamName string) ([]fieldDefinition, error) { fieldsPath := filepath.Join(modulePath, dataStreamName, "_meta", "fields.yml") fs, err := loadFieldsFile(fieldsPath) if err != nil { return nil, errors.Wrapf(err, "loading data stream fields file failed") } for i, f := range fs { fs[i].Name = fmt.Sprintf("%s.%s", moduleName, f.Name) } fieldsEpr := filepath.Join(modulePath, dataStreamName, "_meta", "fields.epr.yml") efs, err := loadFieldsFile(fieldsEpr) if err != nil { return nil, errors.Wrapf(err, "loading fields.epr.yml file failed") } fs = append(fs, efs...) return fs, nil } func loadFieldsFile(path string) ([]fieldDefinition, error) { fields, err := ioutil.ReadFile(path) if os.IsNotExist(err) { return []fieldDefinition{}, nil // return empty array, this is a valid state } if err != nil { return nil, errors.Wrapf(err, "reading fields failed (path: %s)", path) } var fs fieldDefinitionArray err = yaml.Unmarshal(fields, &fs) if err != nil { return nil, errors.Wrapf(err, "unmarshalling fields file failed (path: %s)", path) } fs = loadDefaultFieldValues(fs) return fs, nil } func loadDefaultFieldValues(fs fieldDefinitionArray) fieldDefinitionArray { var withDefaults fieldDefinitionArray for _, f := range fs { if f.Type == "" { f.Type = "keyword" } f.Fields = loadDefaultFieldValues(f.Fields) withDefaults = append(withDefaults, f) } return withDefaults } // filterMigratedFields method filters out fields with "migration: true" property or if it's defined in ECS. // It returns a migrated fields file and found ECS fields. func filterMigratedFields(fields []fieldDefinition, ecsFieldNames []string) ([]fieldDefinition, []string, error) { var filteredEcsFieldNames []string for i, f := range fields { fields[i], filteredEcsFieldNames = visitFieldForFilteringMigrated(f, ecsFieldNames, filteredEcsFieldNames) } return fields, filteredEcsFieldNames, nil } func visitFieldForFilteringMigrated(f fieldDefinition, ecsFieldNames, filteredEcsFieldNames []string) (fieldDefinition, []string) { if len(f.Fields) == 0 { // this field is not a group entry if f.Type == "alias" { if f.Migration != nil && *f.Migration { f.skipped = true // skip the field } for _, ecsFieldName := range ecsFieldNames { if ecsFieldName == f.Path { filteredEcsFieldNames = append(filteredEcsFieldNames, ecsFieldName) f.skipped = true break } } } return f, filteredEcsFieldNames } var updated fieldDefinitionArray for _, fieldsEntry := range f.Fields { var v fieldDefinition v, filteredEcsFieldNames = visitFieldForFilteringMigrated(fieldsEntry, ecsFieldNames, filteredEcsFieldNames) if !v.skipped { updated = append(updated, v) } } f.Fields = updated return f, filteredEcsFieldNames } func isPackageFields(fileName string) bool { return fileName == "package-fields.yml" } func filterEcsFields(ecsFields fieldDefinitionArray, filteredNames []string) fieldDefinitionArray { var filteredFields fieldDefinitionArray for _, f := range ecsFields { visited, checked := visitEcsFieldsToFilter("", f, filteredNames) if checked { filteredFields = append(filteredFields, visited) } } return filteredFields } func visitEcsFieldsToFilter(namePrefix string, f fieldDefinition, filteredNames []string) (fieldDefinition, bool) { name := namePrefix if namePrefix != "" { name += "." } name += f.Name if len(f.Fields) == 0 && f.Type != "group" { for _, fn := range filteredNames { if fn == name { return f, true } } return f, false } var checked bool var checkedFields fieldDefinitionArray for _, fieldEntry := range f.Fields { visited, fieldChecked := visitEcsFieldsToFilter(name, fieldEntry, filteredNames) if fieldChecked { checkedFields = append(checkedFields, visited) checked = true } } f.Fields = checkedFields return f, checked }