-
Notifications
You must be signed in to change notification settings - Fork 203
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
Prototype targets.merge function #1826
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
--- | ||
canonical: https://grafana.com/docs/alloy/latest/reference/stdlib/targets/ | ||
description: Learn about targets functions | ||
menuTitle: targets | ||
title: targets | ||
--- | ||
|
||
# targets | ||
|
||
The `targets` namespace contains functions related to `list(map(string))` arguments. | ||
They are often used by [prometheus][prom-comp] and [discovery][disc-comp] components. | ||
Refer to [Compatible components][] for a full list of components which export and consume targets. | ||
|
||
[prom-comp]: ../components/prometheus/ | ||
[disc-comp]: ../components/discovery/ | ||
[Compatible components]: ../compatibility/#targets | ||
|
||
## targets.merge | ||
|
||
The `targets.inner_join` function allows you to join two arrays containing maps if certain keys have matching values in both maps. | ||
It takes three inputs: | ||
|
||
* The first two inputs are a of type `list(map(string))`. The keys of the map are strings. | ||
The value for each key could have any Alloy type such as a string, integer, map, or a capsule. | ||
* The third input is an array containing strings. The strings are the keys whose value has to match for maps to be joined. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, the "third input" assumes both targets have a label with the same value. I wonder if it's worth changing the third input to be an array of arrays, where we can set different label names in the LHS and RHS targets. E.g.:
This could reduce the amount of relabels a user would have to do. But I don't know if it's really worth the extra complexity. The users probably have to relabel anyway. |
||
|
||
### Examples | ||
|
||
```alloy | ||
targets.inner_join(discovery.kubernetes.k8s_pods.targets, prometheus.exporter.postgres, ["instance"]) | ||
``` | ||
|
||
```alloy | ||
targets.inner_join(prometheus.exporter.redis.default.targets, [{"instance"="1.1.1.1", "testLabelKey" = "testLabelVal"}], ["instance"]) | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,57 @@ func TestVM_Stdlib(t *testing.T) { | |
{"encoding.from_yaml nil field", "encoding.from_yaml(`foo: null`)", map[string]interface{}{"foo": nil}}, | ||
{"encoding.from_yaml nil array element", `encoding.from_yaml("[0, null]")`, []interface{}{0, nil}}, | ||
{"encoding.from_base64", `encoding.from_base64("Zm9vYmFyMTIzIT8kKiYoKSctPUB+")`, string(`foobar123!?$*&()'-=@~`)}, | ||
|
||
// Map tests | ||
{ | ||
// Basic case. No conflicting key/val pairs. | ||
"targets.merge", | ||
`targets.merge([{"a" = "a1", "b" = "b1"}], [{"a" = "a1", "c" = "c1"}], ["a"])`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the result for invalid input? Imagine the first is a valid map list but the second is nil/invalid type/etc? I imagine it would return the map list, whereas if it is the reverse it would return a blank list? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
[]map[string]interface{}{{"a": "a1", "b": "b1", "c": "c1"}}, | ||
}, | ||
{ | ||
// The first array has 2 maps, each with the same key/val pairs. | ||
"targets.merge", | ||
`targets.merge([{"a" = "a1", "b" = "b1"}, {"a" = "a1", "b" = "b1"}], [{"a" = "a1", "c" = "c1"}], ["a"])`, | ||
[]map[string]interface{}{{"a": "a1", "b": "b1", "c": "c1"}, {"a": "a1", "b": "b1", "c": "c1"}}, | ||
}, | ||
{ | ||
// Non-unique merge criteria. | ||
"targets.merge", | ||
`targets.merge([{"pod" = "a", "lbl" = "q"}, {"pod" = "b", "lbl" = "q"}], [{"pod" = "c", "lbl" = "q"}, {"pod" = "d", "lbl" = "q"}], ["lbl"])`, | ||
[]map[string]interface{}{{"lbl": "q", "pod": "c"}, {"lbl": "q", "pod": "d"}, {"lbl": "q", "pod": "c"}, {"lbl": "q", "pod": "d"}}, | ||
}, | ||
{ | ||
// Basic case. Integer and string values. | ||
"targets.merge", | ||
`targets.merge([{"a" = 1, "b" = 2.2}], [{"a" = 1, "c" = "c1"}], ["a"])`, | ||
[]map[string]interface{}{{"a": 1, "b": 2.2, "c": "c1"}}, | ||
}, | ||
{ | ||
// The second map will override a value from the first. | ||
"targets.merge", | ||
`targets.merge([{"a" = 1, "b" = 2.2}], [{"a" = 1, "b" = "3.3"}], ["a"])`, | ||
[]map[string]interface{}{{"a": 1, "b": "3.3"}}, | ||
}, | ||
{ | ||
// Not enough matches for a join. | ||
"targets.merge", | ||
`targets.merge([{"a" = 1, "b" = 2.2}], [{"a" = 2, "b" = "3.3"}], ["a"])`, | ||
[]map[string]interface{}{}, | ||
}, | ||
{ | ||
// Not enough matches for a join. | ||
// The "a" value has differing types. | ||
"targets.merge", | ||
`targets.merge([{"a" = 1, "b" = 2.2}], [{"a" = "1", "b" = "3.3"}], ["a"])`, | ||
[]map[string]interface{}{}, | ||
}, | ||
{ | ||
// Basic case. Some values are arrays and maps. | ||
"targets.merge", | ||
`targets.merge([{"a" = 1, "b" = [1,2,3]}], [{"a" = 1, "c" = {"d" = {"e" = 10}}}], ["a"])`, | ||
[]map[string]interface{}{{"a": 1, "b": []interface{}{1, 2, 3}, "c": map[string]interface{}{"d": map[string]interface{}{"e": 10}}}}, | ||
}, | ||
} | ||
|
||
for _, tc := range tt { | ||
|
@@ -55,6 +106,41 @@ func TestVM_Stdlib(t *testing.T) { | |
} | ||
} | ||
|
||
func TestVM_Stdlib_Errors(t *testing.T) { | ||
tt := []struct { | ||
name string | ||
input string | ||
expectedErr string | ||
}{ | ||
// Map tests | ||
{ | ||
// Error: invalid RHS type - string. | ||
"targets.merge", | ||
`targets.merge([{"a" = "a1", "b" = "b1"}], "a", ["a"])`, | ||
`"a" should be array, got string`, | ||
}, | ||
{ | ||
// Error: invalid RHS type - an array with strings. | ||
"targets.merge", | ||
`targets.merge([{"a" = "a1", "b" = "b1"}], ["a"], ["a"])`, | ||
`"a" should be object, got string`, | ||
}, | ||
} | ||
|
||
for _, tc := range tt { | ||
t.Run(tc.name, func(t *testing.T) { | ||
expr, err := parser.ParseExpression(tc.input) | ||
require.NoError(t, err) | ||
|
||
eval := vm.New(expr) | ||
|
||
rv := reflect.New(reflect.TypeOf([]map[string]interface{}{})) | ||
err = eval.Evaluate(nil, rv.Interface()) | ||
require.ErrorContains(t, err, tc.expectedErr) | ||
}) | ||
} | ||
} | ||
|
||
func TestStdlibCoalesce(t *testing.T) { | ||
t.Setenv("TEST_VAR2", "Hello!") | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to rename to
targets.merge
here and in all other places in the doc.