Skip to content

Commit f26a319

Browse files
authored
Support camelcase matchLabels and matchExpressions in TA config (#3418)
* unmarshal camelcase matchLabels and matchExpressions in target allocator config * fix readPath comment * using mapstructure for flexible unmarshalling * revert deafultconfigfilepath * replacing camel case matchExpression and matchLabel to lower case in ta config unmarshalling * unmarshalling ta config with mapstructure decoder * fix linting issue * moving mapstructure import to the right require block
1 parent 2f37aa2 commit f26a319

8 files changed

+480
-1
lines changed

.chloggen/3350-ta-matchlabels.yaml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: enhancement
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
5+
component: target allocator
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: "Support camelcase matchLabels and matchExpressions in target allocator config"
9+
10+
# One or more tracking issues related to the change
11+
issues: [3350]
12+
13+
# (Optional) One or more lines of additional information to render under the primary note.
14+
# These lines will be padded with 2 spaces and then inserted directly into the document.
15+
# Use pipe (|) for multiline entries.
16+
subtext:

cmd/otel-allocator/config/config.go

+125-1
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ import (
2121
"fmt"
2222
"io/fs"
2323
"os"
24+
"reflect"
2425
"time"
2526

2627
"github.com/go-logr/logr"
28+
"github.com/go-viper/mapstructure/v2"
2729
"github.com/prometheus/common/model"
2830
promconfig "github.com/prometheus/prometheus/config"
2931
_ "github.com/prometheus/prometheus/discovery/install"
@@ -80,6 +82,101 @@ type HTTPSServerConfig struct {
8082
TLSKeyFilePath string `yaml:"tls_key_file_path,omitempty"`
8183
}
8284

85+
// StringToModelDurationHookFunc returns a DecodeHookFuncType
86+
// that converts string to time.Duration, which can be used
87+
// as model.Duration.
88+
func StringToModelDurationHookFunc() mapstructure.DecodeHookFuncType {
89+
return func(
90+
f reflect.Type,
91+
t reflect.Type,
92+
data interface{},
93+
) (interface{}, error) {
94+
if f.Kind() != reflect.String {
95+
return data, nil
96+
}
97+
98+
if t != reflect.TypeOf(model.Duration(5)) {
99+
return data, nil
100+
}
101+
102+
return time.ParseDuration(data.(string))
103+
}
104+
}
105+
106+
// MapToPromConfig returns a DecodeHookFuncType that provides a mechanism
107+
// for decoding promconfig.Config involving its own unmarshal logic.
108+
func MapToPromConfig() mapstructure.DecodeHookFuncType {
109+
return func(
110+
f reflect.Type,
111+
t reflect.Type,
112+
data interface{},
113+
) (interface{}, error) {
114+
if f.Kind() != reflect.Map {
115+
return data, nil
116+
}
117+
118+
if t != reflect.TypeOf(&promconfig.Config{}) {
119+
return data, nil
120+
}
121+
122+
pConfig := &promconfig.Config{}
123+
124+
mb, err := yaml.Marshal(data.(map[any]any))
125+
if err != nil {
126+
return nil, err
127+
}
128+
129+
err = yaml.Unmarshal(mb, pConfig)
130+
if err != nil {
131+
return nil, err
132+
}
133+
return pConfig, nil
134+
}
135+
}
136+
137+
// MapToLabelSelector returns a DecodeHookFuncType that
138+
// provides a mechanism for decoding both matchLabels and matchExpressions from camelcase to lowercase
139+
// because we use yaml unmarshaling that supports lowercase field names if no `yaml` tag is defined
140+
// and metav1.LabelSelector uses `json` tags.
141+
// If both the camelcase and lowercase version is present, then the camelcase version takes precedence.
142+
func MapToLabelSelector() mapstructure.DecodeHookFuncType {
143+
return func(
144+
f reflect.Type,
145+
t reflect.Type,
146+
data interface{},
147+
) (interface{}, error) {
148+
if f.Kind() != reflect.Map {
149+
return data, nil
150+
}
151+
152+
if t != reflect.TypeOf(&metav1.LabelSelector{}) {
153+
return data, nil
154+
}
155+
156+
result := &metav1.LabelSelector{}
157+
fMap := data.(map[any]any)
158+
if matchLabels, ok := fMap["matchLabels"]; ok {
159+
fMap["matchlabels"] = matchLabels
160+
delete(fMap, "matchLabels")
161+
}
162+
if matchExpressions, ok := fMap["matchExpressions"]; ok {
163+
fMap["matchexpressions"] = matchExpressions
164+
delete(fMap, "matchExpressions")
165+
}
166+
167+
b, err := yaml.Marshal(fMap)
168+
if err != nil {
169+
return nil, err
170+
}
171+
172+
err = yaml.Unmarshal(b, result)
173+
if err != nil {
174+
return nil, err
175+
}
176+
return result, nil
177+
}
178+
}
179+
83180
func LoadFromFile(file string, target *Config) error {
84181
return unmarshal(target, file)
85182
}
@@ -153,14 +250,41 @@ func LoadFromCLI(target *Config, flagSet *pflag.FlagSet) error {
153250
return nil
154251
}
155252

253+
// unmarshal decodes the contents of the configFile into the cfg argument, using a
254+
// mapstructure decoder with the following notable behaviors.
255+
// Decodes time.Duration from strings (see StringToModelDurationHookFunc).
256+
// Allows custom unmarshaling for promconfig.Config struct that implements yaml.Unmarshaler (see MapToPromConfig).
257+
// Allows custom unmarshaling for metav1.LabelSelector struct using both camelcase and lowercase field names (see MapToLabelSelector).
156258
func unmarshal(cfg *Config, configFile string) error {
157259
yamlFile, err := os.ReadFile(configFile)
158260
if err != nil {
159261
return err
160262
}
161-
if err = yaml.Unmarshal(yamlFile, cfg); err != nil {
263+
264+
m := make(map[string]interface{})
265+
err = yaml.Unmarshal(yamlFile, &m)
266+
if err != nil {
162267
return fmt.Errorf("error unmarshaling YAML: %w", err)
163268
}
269+
270+
dc := mapstructure.DecoderConfig{
271+
TagName: "yaml",
272+
Result: cfg,
273+
DecodeHook: mapstructure.ComposeDecodeHookFunc(
274+
StringToModelDurationHookFunc(),
275+
MapToPromConfig(),
276+
MapToLabelSelector(),
277+
),
278+
}
279+
280+
decoder, err := mapstructure.NewDecoder(&dc)
281+
if err != nil {
282+
return err
283+
}
284+
if err := decoder.Decode(m); err != nil {
285+
return err
286+
}
287+
164288
return nil
165289
}
166290

0 commit comments

Comments
 (0)