Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce checks for not supported annotations for Hints based autodiscover of Elastic Agent #81

Merged
merged 13 commits into from
Mar 13, 2024
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
27 changes: 25 additions & 2 deletions utils/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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])
Expand All @@ -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
Expand Down Expand Up @@ -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
}
46 changes: 39 additions & 7 deletions utils/hints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,33 @@ func TestGetProcessors(t *testing.T) {
}

func TestGenerateHints(t *testing.T) {
const (
integration = "package"
datastreams = "data_streams"
host = "host"
period = "period"
timeout = "timeout"
metricspath = "metrics_path"
username = "username"
password = "password"
stream = "stream" // this is the container stream: stdout/stderr
processors = "processors"
)

var allSupportedHints = []string{"enabled", "module", integration, datastreams, host, period, timeout, metricspath, 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:
Expand All @@ -68,6 +87,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",
Expand All @@ -91,13 +111,15 @@ func TestGenerateHints(t *testing.T) {
"period": "15s",
},
},
expectedIncorrectHints: 0,
},
// Scenarios being tested:
// 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
// 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",
Expand All @@ -107,6 +129,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{
Expand All @@ -115,6 +138,7 @@ func TestGenerateHints(t *testing.T) {
"pattern": "^test",
},
},
"hints": mapstr.M{"steam": "stdout"},
"metrics": mapstr.M{
"module": "prometheus",
"period": "15s",
Expand All @@ -123,14 +147,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",
Expand All @@ -150,6 +175,7 @@ func TestGenerateHints(t *testing.T) {
"period": "15s",
},
},
expectedIncorrectHints: 0,
},
// Scenarios being tested:
// have co.elastic.logs/disable set to false.
Expand All @@ -158,6 +184,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",
Expand All @@ -179,6 +206,7 @@ func TestGenerateHints(t *testing.T) {
"period": "15s",
},
},
expectedIncorrectHints: 0,
},
// Scenarios being tested:
// have co.elastic.logs/disable set to true.
Expand All @@ -187,6 +215,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",
Expand All @@ -208,6 +237,7 @@ func TestGenerateHints(t *testing.T) {
"period": "15s",
},
},
expectedIncorrectHints: 0,
},
}

Expand All @@ -219,7 +249,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) {
Expand Down
Loading