Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2c3f76e
Add dynamic templates as parameter
mrodm Dec 12, 2024
41d7b88
Add validation for each dynamic template depending on the parameters
mrodm Dec 16, 2024
ff5298e
Fixes for dynamic templates
mrodm Dec 16, 2024
af04d66
Compare fields with dynamic templates
mrodm Dec 17, 2024
f21c712
Remove multi_fields from flattened fields
mrodm Dec 17, 2024
aa28db6
Test without filtering dynamic templates
mrodm Dec 17, 2024
3767111
Ensure properties subfields are validated accordingly
mrodm Dec 18, 2024
9fb80b4
Restore filtering and add tests
mrodm Dec 18, 2024
2ad28ac
Disable unmatch_mapping_type and match_mapping_type and continue look…
mrodm Dec 19, 2024
8bdad65
Merge upstream/main into validate-dynamic-mappings
mrodm Dec 19, 2024
151e59e
Refactors and remove log statements
mrodm Dec 19, 2024
d0ff6b9
Fix function naming
mrodm Dec 19, 2024
b474b4b
Add test with match_pattern regex
mrodm Dec 19, 2024
066b5a0
Add comment in tests
mrodm Dec 19, 2024
a7eb191
Remove loading schema from create validator for mappings method
mrodm Jan 7, 2025
7666ac2
Separate parsing and validation of dynamic templates
mrodm Jan 7, 2025
a718796
Support match_pattern for the other settings
mrodm Jan 7, 2025
0a5e775
fix test
mrodm Jan 8, 2025
131cc9c
Update tests for multi-fields
mrodm Jan 8, 2025
38db8df
Review multi-fields logic
mrodm Jan 8, 2025
9ff3d0c
Revisit multi-fields logic in ECS
mrodm Jan 8, 2025
5165612
Report all errors related to multi-fields comparing with ECS
mrodm Jan 8, 2025
ce557c9
Ignored validation of multi-fields with ECS
mrodm Jan 8, 2025
ebda859
Fix multi-field test
mrodm Jan 8, 2025
019ffd4
Rephrase errors
mrodm Jan 9, 2025
8f36c18
Validate fully dynamic objects (preview) with dynamic templates
mrodm Jan 9, 2025
d96ffbf
Rephrase debug message
mrodm Jan 9, 2025
dbca4fe
Add logging - to be removed
mrodm Jan 9, 2025
8fce0ec
Merge remote-tracking branch 'upstream/main' into validate-dynamic-ma…
mrodm Jan 14, 2025
c333707
Revisited log messages
mrodm Jan 20, 2025
0569cfb
Remove comments
mrodm Jan 20, 2025
1aea303
Remove more debug statements
mrodm Jan 20, 2025
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
Prev Previous commit
Next Next commit
Add validation for each dynamic template depending on the parameters
  • Loading branch information
mrodm committed Dec 16, 2024
commit 41d7b8878f42cd0cca57f256cccc2f95fc7718eb
232 changes: 226 additions & 6 deletions internal/fields/mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"path/filepath"
"regexp"
"slices"
"strings"

Expand Down Expand Up @@ -241,7 +242,7 @@ func (v *MappingValidator) ValidateIndexMappings(ctx context.Context) multierror
return errs.Unique()
}

var rawDynamicTemplates map[string]any
var rawDynamicTemplates []map[string]any
err = json.Unmarshal(actualDynamicTemplates, &rawDynamicTemplates)
if err != nil {
errs = append(errs, fmt.Errorf("failed to unmarshal actual dynamic templates (data stream %s): %w", v.dataStreamName, err))
Expand Down Expand Up @@ -488,7 +489,7 @@ func validateConstantKeywordField(path string, preview, actual map[string]any) (
return isConstantKeyword, nil
}

func (v *MappingValidator) compareMappings(path string, preview, actual, dynamicTemplates map[string]any) multierror.Error {
func (v *MappingValidator) compareMappings(path string, preview, actual map[string]any, dynamicTemplates []map[string]any) multierror.Error {
var errs multierror.Error

isConstantKeywordType, err := validateConstantKeywordField(path, preview, actual)
Expand Down Expand Up @@ -563,7 +564,7 @@ func (v *MappingValidator) compareMappings(path string, preview, actual, dynamic
return errs.Unique()
}

func (v *MappingValidator) validateObjectProperties(path string, containsMultifield bool, actual, preview, dynamicTemplates map[string]any) multierror.Error {
func (v *MappingValidator) validateObjectProperties(path string, containsMultifield bool, actual, preview map[string]any, dynamicTemplates []map[string]any) multierror.Error {
var errs multierror.Error
for key, value := range actual {
if containsMultifield && key == "fields" {
Expand Down Expand Up @@ -601,7 +602,7 @@ func (v *MappingValidator) validateObjectProperties(path string, containsMultifi

// validateMappingsNotInPreview validates the object and the nested objects in the current path with other resources
// like ECS schema, dynamic templates or local fields defined in the package (type array).
func (v *MappingValidator) validateMappingsNotInPreview(currentPath string, childField, dynamicTemplates map[string]any) multierror.Error {
func (v *MappingValidator) validateMappingsNotInPreview(currentPath string, childField map[string]any, dynamicTemplates []map[string]any) multierror.Error {
var errs multierror.Error
flattenFields, err := flattenMappings(currentPath, childField)
if err != nil {
Expand All @@ -611,7 +612,7 @@ func (v *MappingValidator) validateMappingsNotInPreview(currentPath string, chil

for fieldPath, object := range flattenFields {
if slices.Contains(v.exceptionFields, fieldPath) {
logger.Warnf("Found exception field, skip its validation: %q", fieldPath)
logger.Warnf("Found exception field, skip its validation (not in preview): %q", fieldPath)
return nil
}

Expand All @@ -628,6 +629,12 @@ func (v *MappingValidator) validateMappingsNotInPreview(currentPath string, chil

// TODO: validate mapping with dynamic templates first than validating with ECS
// just raise an error if both validation processes fail
matched, err := v.matchingWithDynamicTemplates(fieldPath, def, dynamicTemplates)
if err == nil {
// TODO
logger.Debugf("Matched: %t", matched)
continue
}

// are all fields under this key defined in ECS?
err = v.validateMappingInECSSchema(fieldPath, def)
Expand All @@ -639,9 +646,222 @@ func (v *MappingValidator) validateMappingsNotInPreview(currentPath string, chil
return errs.Unique()
}

func (v *MappingValidator) matchingWithDynamicTemplates(currentPath string, definition map[string]any, dynamicTemplates []map[string]any) (bool, error) {

parseSetting := func(value any) ([]string, error) {
all := []string{}
switch v := value.(type) {
case []any:
for _, elem := range v {
s, ok := elem.(string)
if !ok {
return nil, fmt.Errorf("failed to cast to string: %s", elem)
}
all = append(all, s)
}
case any:
s, ok := v.(string)
if !ok {
return nil, fmt.Errorf("failed to cast to string: %s", v)
}
all = append(all, s)
default:
return nil, fmt.Errorf("unexpected type for setting: %T", value)

}
return all, nil
}

fieldName := func(path string) string {
if !strings.Contains(path, ".") {
return path
}

elems := strings.Split(path, ".")
return elems[len(elems)-1]
}

stringMatchesPatterns := func(regexes []string, elem string, fullRegex bool) (bool, error) {
applies := false
for _, v := range regexes {
if !strings.Contains(v, "*") {
// not a regex
continue
}

var r string
if fullRegex {
r = v
} else {
r = strings.ReplaceAll(v, ".", "\\.")
r = strings.ReplaceAll(r, "*", ".*")
}

match, err := regexp.MatchString(r, elem)
if err != nil {
return false, fmt.Errorf("failed to build regex %s: %w", r, err)
}
if match {
applies = true
break
}
}
return applies, nil
}

fieldType := mappingParameter("type", definition)
if fieldType == "" {
return false, fmt.Errorf("missing type for the field %s", currentPath)
}

for _, template := range dynamicTemplates {
if len(template) != 1 {
return false, fmt.Errorf("unexpected number of dynamic template definitions found")
}

templateName := ""
var rawContents any
for key, value := range template {
templateName = key
rawContents = value
}
logger.Debugf("Checking dynamic template for %q: %q", currentPath, templateName)

contents, ok := rawContents.(map[string]any)
if !ok {
return false, fmt.Errorf("unexpected dynamic template format found for %s", templateName)
}

fullRegex := false
if v, ok := contents["match_pattern"]; ok {
s, ok := v.(string)
if !ok {
return false, fmt.Errorf("invalid type for \"match_pattern\": %T", v)
}
if s == "regex" {
logger.Debugf("Use full regex in dynamic templates (match_pattern: regex)")
fullRegex = true
}
}

// matches with the current definitions and path
// https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html
matched := true
for setting, value := range contents {
switch setting {
case "mapping":
// Skip
case "match_pattern":
// Skip
case "match":
name := fieldName(currentPath)
logger.Debugf("> Check match: %q (key %q)", currentPath, name)
values, err := parseSetting(value)
if err != nil {
return false, fmt.Errorf("failed to check match setting: %w", err)
}
if !slices.Contains(values, name) {
matched = false
break
}
matches, err := stringMatchesPatterns(values, name, fullRegex)
if err != nil {
return false, fmt.Errorf("failed to parse dynamic template %s: %w", templateName, err)
}
if !matches {
logger.Debugf(">> Issue: not matches")
matched = false
break
}
case "unmatch":
name := fieldName(currentPath)
logger.Debugf("> Check unmatch: %q (key %q)", currentPath, name)
values, err := parseSetting(value)
if err != nil {
return false, fmt.Errorf("failed to check match setting: %w", err)
}
if slices.Contains(values, name) {
matched = false
break
}
matches, err := stringMatchesPatterns(values, name, fullRegex)
if err != nil {
return false, fmt.Errorf("failed to parse dynamic template %s: %w", templateName, err)
}
if matches {
logger.Debugf(">> Issue: matches")
matched = false
break
}
case "path_match":
logger.Debugf("> Check path_match: %q", currentPath)
values, err := parseSetting(value)
matches, err := stringMatchesPatterns(values, currentPath, false)
if err != nil {
return false, fmt.Errorf("failed to parse dynamic template %s: %w", templateName, err)
}
if !matches {
logger.Debugf(">> Issue: not matches")
matched = false
break
}
case "path_unmatch":
logger.Debugf("> Check path_unmatch: %q", currentPath)
values, err := parseSetting(value)
if err != nil {
return false, fmt.Errorf("failed to check path_unmatch setting: %w", err)
}
matches, err := stringMatchesPatterns(values, currentPath, false)
if err != nil {
return false, fmt.Errorf("failed to parse dynamic template %s: %w", templateName, err)
}
if matches {
logger.Debugf(">> Issue: matches")
matched = false
break
}
case "match_mapping_type":
logger.Debugf("> Check match_mapping_type: %q (type %s vs mapping type %q)", currentPath, definition, fieldType)
values, err := parseSetting(value)
if err != nil {
return false, fmt.Errorf("failed to check match_mapping_type setting: %w", err)
}
if slices.Contains(values, "*") {
continue
}
if !slices.Contains(values, fieldType) {
logger.Debugf(">> Issue: not matches")
matched = false
break
}
case "unmatch_mapping_type":
logger.Debugf("> Check unmatch_mapping_type: %q (type %s vs mapping type %q)", currentPath, definition, fieldType)
values, err := parseSetting(value)
if err != nil {
return false, fmt.Errorf("failed to check unmatch_mapping_type setting: %w", err)
}
if slices.Contains(values, fieldType) {
logger.Debugf(">> Issue: matches")
matched = false
break
}
default:
return false, fmt.Errorf("unexpected setting found in dynamic template")
}
}
if matched {
logger.Debugf("> Found dynamic template matched: %s", templateName)
return true, nil
}
}

logger.Debugf(">>> No template matching")
return false, nil
}

// validateObjectMappingAndParameters validates the current object or field parameter (currentPath) comparing the values
// in the actual mapping with the values in the preview mapping.
func (v *MappingValidator) validateObjectMappingAndParameters(previewValue, actualValue any, currentPath string, dynamicTemplates map[string]any) multierror.Error {
func (v *MappingValidator) validateObjectMappingAndParameters(previewValue, actualValue any, currentPath string, dynamicTemplates []map[string]any) multierror.Error {
var errs multierror.Error
switch actualValue.(type) {
case map[string]any:
Expand Down
2 changes: 1 addition & 1 deletion internal/fields/mappings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ func TestComparingMappings(t *testing.T) {

for _, c := range cases {
t.Run(c.title, func(t *testing.T) {
dynamicTemplates := map[string]any{}
dynamicTemplates := []map[string]any{}

logger.EnableDebugMode()
specVersion := defaultSpecVersion
Expand Down