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
}
36 changes: 33 additions & 3 deletions utils/hints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,29 @@ 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 {
name string
annotations map[string]string
result mapstr.M
}{
// Empty annotations should return empty hints
//Empty annotations should return empty hints
{
name: "Empty_Annotations",
annotations: map[string]string{},
result: mapstr.M{},
},
Expand All @@ -68,6 +85,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 Down Expand Up @@ -98,6 +116,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",
Expand All @@ -107,6 +126,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 +135,7 @@ func TestGenerateHints(t *testing.T) {
"pattern": "^test",
},
},
"hints": mapstr.M{"steam": "stdout"},
"metrics": mapstr.M{
"module": "prometheus",
"period": "15s",
Expand All @@ -125,12 +146,12 @@ func TestGenerateHints(t *testing.T) {
},
},
// 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 Down Expand Up @@ -158,6 +179,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 Down Expand Up @@ -187,6 +209,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 Down Expand Up @@ -219,7 +242,14 @@ func TestGenerateHints(t *testing.T) {
continue
}
}
assert.Equal(t, test.result, GenerateHints(annMap, "foobar", "co.elastic"))
generateHints, incorrectHints := GenerateHints(annMap, "foobar", "co.elastic", allSupportedHints)
//Only in Logs_multiline_and_metrics_with_metrics_path we have added co.elastic.hints.steam annotation with a typo error
if test.name == "Logs_multiline_and_metrics_with_metrics_path" {
assert.Equal(t, 1, len(incorrectHints)) // We validate how many incorrect hints are provided in test1.
gizas marked this conversation as resolved.
Show resolved Hide resolved
} else {
assert.Equal(t, 0, len(incorrectHints)) // We validate how many incorrect hints are provided in rest of tests
}
assert.Equal(t, test.result, generateHints)
}
}
func TestGetHintsAsList(t *testing.T) {
Expand Down
Loading