forked from open-telemetry/opentelemetry-collector-contrib
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config_expansion.go
144 lines (131 loc) · 4.65 KB
/
config_expansion.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receivercreator // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/receivercreator"
import (
"errors"
"fmt"
"strings"
"github.com/antonmedv/expr"
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer"
)
// evalBackticksInConfigValue expands any expressions within backticks inside configValue
// using variables from env.
//
// Note that when evaluating multiple expressions that the expanded result is always
// a string. For instance:
//
// `true``false` -> "truefalse"
//
// However if there is only one expansion then the expanded result will keep the type
// of the expression. For instance:
//
// `"secure" in pod.labels` -> true (boolean)
func evalBackticksInConfigValue(configValue string, env observer.EndpointEnv) (interface{}, error) {
// Tracks index into configValue where an expression (backtick) begins. -1 is unset.
exprStartIndex := -1
// Accumulate expanded string.
output := &strings.Builder{}
// Accumulate results of calls to eval for use at the end to return well-typed
// results if possible.
var expansions []interface{}
// Loop through configValue one rune at a time using exprStartIndex to keep track of
// inside or outside of expressions.
for i := 0; i < len(configValue); i++ {
switch configValue[i] {
case '\\':
if i+1 == len(configValue) {
return nil, errors.New(`encountered escape (\) without value at end of expression`)
}
output.WriteByte(configValue[i+1])
i++
case '`':
if exprStartIndex == -1 {
// Opening backtick encountered, expression starts one after current index.
exprStartIndex = i + 1
} else {
// Closing backtick encountered, evaluate the expression.
exprText := configValue[exprStartIndex:i]
// If expression has no text inside it return an error.
if strings.TrimSpace(exprText) == "" {
return nil, errors.New("expression is empty")
}
res, err := expr.Eval(exprText, env)
if err != nil {
return nil, err
}
expansions = append(expansions, res)
_, _ = fmt.Fprintf(output, "%v", res)
// Reset start index since this expression just closed.
exprStartIndex = -1
}
default:
if exprStartIndex == -1 {
output.WriteByte(configValue[i])
}
}
}
// Should always be a closing backtick if it's balanced.
if exprStartIndex != -1 {
return nil, fmt.Errorf("expression was unbalanced starting at character %d", exprStartIndex)
}
// If there was only one expansion and it is equal to the full output string return the expansion
// itself so that it retains the type returned by the expression. Might be a bool, int, etc.
// instead of a string.
if len(expansions) == 1 && output.String() == fmt.Sprintf("%v", expansions[0]) {
return expansions[0], nil
}
return output.String(), nil
}
// expandConfig will walk the provided user config and expand any `backticked` content
// with associated observer.EndpointEnv values.
func expandConfig(cfg userConfigMap, env observer.EndpointEnv) (userConfigMap, error) {
expanded, err := expandAny(map[string]interface{}(cfg), env)
if err != nil {
return nil, err
}
return expanded.(map[string]interface{}), nil
}
// expandAny recursively expands any expressions in backticks inside values of input using
// env as variables available within the expression, returning a copy of input
func expandAny(input interface{}, env observer.EndpointEnv) (interface{}, error) {
switch v := input.(type) {
case string:
res, err := evalBackticksInConfigValue(v, env)
if err != nil {
return nil, fmt.Errorf("failed evaluating config expression for %v: %w", v, err)
}
return res, nil
case []string, []interface{}:
var vSlice []interface{}
if vss, ok := v.([]string); ok {
// expanded strings aren't guaranteed to remain them, so we
// coerce to interface{} for shared []interface{} expansion path
for _, vs := range vss {
vSlice = append(vSlice, vs)
}
} else {
vSlice = v.([]interface{})
}
expandedSlice := make([]interface{}, 0, len(vSlice))
for _, val := range vSlice {
expanded, err := expandAny(val, env)
if err != nil {
return nil, fmt.Errorf("failed evaluating config expression for %v: %w", val, err)
}
expandedSlice = append(expandedSlice, expanded)
}
return expandedSlice, nil
case map[string]interface{}:
expandedMap := map[string]interface{}{}
for key, val := range v {
expandedVal, err := expandAny(val, env)
if err != nil {
return nil, fmt.Errorf("failed evaluating config expression for {%q: %v}: %w", key, val, err)
}
expandedMap[key] = expandedVal
}
return expandedMap, nil
default:
return v, nil
}
}