// 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 common import ( "fmt" "strings" "github.com/elastic/go-ucfg/yaml" ) //This reflects allowed attributes for field definitions in the fields.yml. //No logic is put into this data structure. //The purpose is to enable using different kinds of transformation, on top of the same data structure. //Current transformation: // -ElasticSearch Template // -Kibana Index Pattern type Fields []Field type Field struct { Name string `config:"name"` Type string `config:"type"` Description string `config:"description"` Format string `config:"format"` ScalingFactor int `config:"scaling_factor"` Fields Fields `config:"fields"` MultiFields Fields `config:"multi_fields"` ObjectType string `config:"object_type"` ObjectTypeMappingType string `config:"object_type_mapping_type"` Enabled *bool `config:"enabled"` Analyzer string `config:"analyzer"` SearchAnalyzer string `config:"search_analyzer"` Norms bool `config:"norms"` Dynamic DynamicType `config:"dynamic"` Index *bool `config:"index"` DocValues *bool `config:"doc_values"` CopyTo string `config:"copy_to"` IgnoreAbove int `config:"ignore_above"` AliasPath string `config:"path"` // Kibana specific Analyzed *bool `config:"analyzed"` Count int `config:"count"` Searchable *bool `config:"searchable"` Aggregatable *bool `config:"aggregatable"` Script string `config:"script"` // Kibana params Pattern string `config:"pattern"` InputFormat string `config:"input_format"` OutputFormat string `config:"output_format"` OutputPrecision *int `config:"output_precision"` LabelTemplate string `config:"label_template"` UrlTemplate []VersionizedString `config:"url_template"` OpenLinkInCurrentTab *bool `config:"open_link_in_current_tab"` Path string } type VersionizedString struct { MinVersion string `config:"min_version"` Value string `config:"value"` } type DynamicType struct{ Value interface{} } func (d *DynamicType) Unpack(s string) error { switch s { case "true": d.Value = true case "false": d.Value = false case "strict": d.Value = s default: return fmt.Errorf("'%v' is invalid dynamic setting", s) } return nil } func LoadFieldsYaml(path string) (Fields, error) { keys := []Field{} cfg, err := yaml.NewConfigWithFile(path) if err != nil { return nil, err } cfg.Unpack(&keys) fields := Fields{} for _, key := range keys { fields = append(fields, key.Fields...) } return fields, nil } // HasKey checks if inside fields the given key exists // The key can be in the form of a.b.c and it will check if the nested field exist // In case the key is `a` and there is a value `a.b` false is return as it only // returns true if it's a leave node func (f Fields) HasKey(key string) bool { keys := strings.Split(key, ".") return f.hasKey(keys) } // HasNode checks if inside fields the given node exists // In contrast to HasKey it not only compares the leaf nodes but // every single key it traverses. func (f Fields) HasNode(key string) bool { keys := strings.Split(key, ".") return f.hasNode(keys) } func (f Fields) hasNode(keys []string) bool { // Nothing to compare, so does not contain it if len(keys) == 0 { return false } key := keys[0] keys = keys[1:] for _, field := range f { if field.Name == key { //// It's the last key to compare if len(keys) == 0 { return true } // It's the last field to compare if len(field.Fields) == 0 { return true } return field.Fields.hasNode(keys) } } return false } // Recursively generates the correct key based on the dots // The mapping requires "properties" between each layer. This is added here. func GenerateKey(key string) string { if strings.Contains(key, ".") { keys := strings.SplitN(key, ".", 2) key = keys[0] + ".properties." + GenerateKey(keys[1]) } return key } func (f Fields) hasKey(keys []string) bool { // Nothing to compare anymore if len(keys) == 0 { return false } key := keys[0] keys = keys[1:] for _, field := range f { if field.Name == key { if len(field.Fields) > 0 { return field.Fields.hasKey(keys) } // Last entry in the tree but still more keys if len(keys) > 0 { return false } return true } } return false } // GetKeys returns a flat list of keys this Fields contains func (f Fields) GetKeys() []string { return f.getKeys("") } func (f Fields) getKeys(namespace string) []string { var keys []string for _, field := range f { fieldName := namespace + "." + field.Name if namespace == "" { fieldName = field.Name } if len(field.Fields) == 0 { keys = append(keys, fieldName) } else { keys = append(keys, field.Fields.getKeys(fieldName)...) } } return keys }