diff --git a/CHANGELOG.md b/CHANGELOG.md index e378d2ea9f..247ac580f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,3 +44,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). [0.6.7]: https://github.com/elastic/elastic-agent-autodiscover/compare/v0.6.2...v0.6.7 + +## [0.6.9] + +### Changed + +- Update GenerateHints function to check supported list of hints + + +[0.6.9]: https://github.com/elastic/elastic-agent-autodiscover/compare/v0.6.8...v0.6.9 diff --git a/utils/hints.go b/utils/hints.go index f3b816b539..cde1ae185c 100644 --- a/utils/hints.go +++ b/utils/hints.go @@ -202,16 +202,22 @@ func IsDisabled(hints mapstr.M, key string) bool { } // GenerateHints parses annotations based on a prefix and sets up hints that can be picked up by individual Beats. -func GenerateHints(annotations mapstr.M, container, prefix string) mapstr.M { +func GenerateHints(annotations mapstr.M, container, prefix string, allSupportedHints []string) (mapstr.M, []string) { hints := mapstr.M{} + var incorrecthints []string + found := false if rawEntries, err := annotations.GetValue(prefix); err == nil { if entries, ok := rawEntries.(mapstr.M); ok { for key, rawValue := range entries { + // If there are top level hints like co.elastic.logs/ then just add the values after the / // Only consider namespaced annotations parts := strings.Split(key, "/") if len(parts) == 2 { hintKey := fmt.Sprintf("%s.%s", parts[0], parts[1]) + //We check whether the provided annotation follows the supported format and vocabulary. The check happens for annotations that have prefix co.elastic + found = checkSupportedHints(parts[1], allSupportedHints) + // Insert only if there is no entry already. container level annotations take // higher priority. if _, err := hints.GetValue(hintKey); err != nil { @@ -233,6 +239,8 @@ func GenerateHints(annotations mapstr.M, container, prefix string) mapstr.M { if strings.HasPrefix(hintKey, container) { // Split the key to get part[1] to be the hint parts := strings.Split(hintKey, "/") + //We check whether the provided annotation follows the supported format and vocabulary. The check happens for annotations that have prefix co.elastic + found = checkSupportedHints(parts[1], allSupportedHints) if len(parts) == 2 { // key will be the hint type hintKey := fmt.Sprintf("%s.%s", key, parts[1]) @@ -244,11 +252,14 @@ func GenerateHints(annotations mapstr.M, container, prefix string) mapstr.M { } } } + if !found { + incorrecthints = append(incorrecthints, key) + } } } } - return hints + return hints, incorrecthints } // GetHintsAsList gets a set of hints and tries to convert them into a list of hints @@ -289,3 +300,15 @@ func GetHintsAsList(hints mapstr.M, key string) []mapstr.M { } return configs } + +// checkSupportedHints gets a specific hint annotation and compares it with the supported list of hints +func checkSupportedHints(actualannotation string, allSupportedHints []string) bool { + found := false + for _, checksupported := range allSupportedHints { + if actualannotation == checksupported { + found = true + break + } + } + return found +} diff --git a/utils/hints_test.go b/utils/hints_test.go index 624bab5456..3072c90b36 100644 --- a/utils/hints_test.go +++ b/utils/hints_test.go @@ -50,14 +50,21 @@ func TestGetProcessors(t *testing.T) { } func TestGenerateHints(t *testing.T) { + + var allSupportedHints = []string{"enabled", "module", "integration", "datas_treams", "host", "period", "timeout", "metrics_path", "username", "password", "stream", "processors", "multiline", "json", "disable"} + tests := []struct { - annotations map[string]string - result mapstr.M + name string + annotations map[string]string + result mapstr.M + expectedIncorrectHints int // We set the number of hints that will be marked as incorrect and wont be included in the acceptable supported list }{ - // Empty annotations should return empty hints + //Empty annotations should return empty hints { - annotations: map[string]string{}, - result: mapstr.M{}, + name: "Empty_Annotations", + annotations: map[string]string{}, + result: mapstr.M{}, + expectedIncorrectHints: 0, }, // Scenarios being tested: @@ -68,6 +75,7 @@ func TestGenerateHints(t *testing.T) { // not.to.include must not be part of hints // period is annotated at both container and pod level. Container level value must be in hints { + name: "Logs_multiline_and_metrics", annotations: map[string]string{ "co.elastic.logs/multiline.pattern": "^test", "co.elastic.logs/json.keys_under_root": "true", @@ -91,6 +99,7 @@ func TestGenerateHints(t *testing.T) { "period": "15s", }, }, + expectedIncorrectHints: 0, }, // Scenarios being tested: // logs/multiline.pattern must be a nested mapstr.M under hints.logs @@ -98,6 +107,7 @@ func TestGenerateHints(t *testing.T) { // not.to.include must not be part of hints // metrics/metrics_path must be found in hints.metrics { + name: "Logs_multiline_and_metrics_with_metrics_path", annotations: map[string]string{ "co.elastic.logs/multiline.pattern": "^test", "co.elastic.metrics/module": "prometheus", @@ -107,6 +117,7 @@ func TestGenerateHints(t *testing.T) { "co.elastic.metrics/password": "pass", "co.elastic.metrics.foobar/period": "15s", "co.elastic.metrics.foobar1/period": "15s", + "co.elastic.hints/steam": "stdout", // On purpose this added with typo "not.to.include": "true", }, result: mapstr.M{ @@ -115,6 +126,7 @@ func TestGenerateHints(t *testing.T) { "pattern": "^test", }, }, + "hints": mapstr.M{"steam": "stdout"}, "metrics": mapstr.M{ "module": "prometheus", "period": "15s", @@ -123,14 +135,15 @@ func TestGenerateHints(t *testing.T) { "password": "pass", }, }, + expectedIncorrectHints: 1, // Due to co.elastic.hints/steam and not co.elastic.hints/stream }, // Scenarios being tested: - // have co.elastic.logs/disable set to false. // logs/multiline.pattern must be a nested mapstr.M under hints.logs // metrics/module must be found in hints.metrics // not.to.include must not be part of hints // period is annotated at both container and pod level. Container level value must be in hints { + name: "Logs_multiline_and_metrics", annotations: map[string]string{ "co.elastic.logs/multiline.pattern": "^test", "co.elastic.metrics/module": "prometheus", @@ -150,6 +163,7 @@ func TestGenerateHints(t *testing.T) { "period": "15s", }, }, + expectedIncorrectHints: 0, }, // Scenarios being tested: // have co.elastic.logs/disable set to false. @@ -158,6 +172,7 @@ func TestGenerateHints(t *testing.T) { // not.to.include must not be part of hints // period is annotated at both container and pod level. Container level value must be in hints { + name: "Logs_disabled_false_and_metrics", annotations: map[string]string{ "co.elastic.logs/disable": "false", "co.elastic.logs/multiline.pattern": "^test", @@ -179,6 +194,7 @@ func TestGenerateHints(t *testing.T) { "period": "15s", }, }, + expectedIncorrectHints: 0, }, // Scenarios being tested: // have co.elastic.logs/disable set to true. @@ -187,6 +203,7 @@ func TestGenerateHints(t *testing.T) { // not.to.include must not be part of hints // period is annotated at both container and pod level. Container level value must be in hints { + name: "Logs_disabled_true_and_metrics", annotations: map[string]string{ "co.elastic.logs/disable": "true", "co.elastic.logs/multiline.pattern": "^test", @@ -208,6 +225,7 @@ func TestGenerateHints(t *testing.T) { "period": "15s", }, }, + expectedIncorrectHints: 0, }, } @@ -219,7 +237,9 @@ func TestGenerateHints(t *testing.T) { continue } } - assert.Equal(t, test.result, GenerateHints(annMap, "foobar", "co.elastic")) + generateHints, incorrectHints := GenerateHints(annMap, "foobar", "co.elastic", allSupportedHints) + assert.Equal(t, test.expectedIncorrectHints, len(incorrectHints)) // We validate how many incorrect hints are provided per test case. + assert.Equal(t, test.result, generateHints) } } func TestGetHintsAsList(t *testing.T) {