Skip to content

Commit

Permalink
Support camelcase matchLabels and matchExpressions in TA config (#3418)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
davidhaja authored Jan 29, 2025
1 parent 2f37aa2 commit f26a319
Show file tree
Hide file tree
Showing 8 changed files with 480 additions and 1 deletion.
16 changes: 16 additions & 0 deletions .chloggen/3350-ta-matchlabels.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
component: target allocator

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: "Support camelcase matchLabels and matchExpressions in target allocator config"

# One or more tracking issues related to the change
issues: [3350]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
126 changes: 125 additions & 1 deletion cmd/otel-allocator/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
"fmt"
"io/fs"
"os"
"reflect"
"time"

"github.com/go-logr/logr"
"github.com/go-viper/mapstructure/v2"
"github.com/prometheus/common/model"
promconfig "github.com/prometheus/prometheus/config"
_ "github.com/prometheus/prometheus/discovery/install"
Expand Down Expand Up @@ -80,6 +82,101 @@ type HTTPSServerConfig struct {
TLSKeyFilePath string `yaml:"tls_key_file_path,omitempty"`
}

// StringToModelDurationHookFunc returns a DecodeHookFuncType
// that converts string to time.Duration, which can be used
// as model.Duration.
func StringToModelDurationHookFunc() mapstructure.DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}

if t != reflect.TypeOf(model.Duration(5)) {
return data, nil
}

return time.ParseDuration(data.(string))
}
}

// MapToPromConfig returns a DecodeHookFuncType that provides a mechanism
// for decoding promconfig.Config involving its own unmarshal logic.
func MapToPromConfig() mapstructure.DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
if f.Kind() != reflect.Map {
return data, nil
}

if t != reflect.TypeOf(&promconfig.Config{}) {
return data, nil
}

pConfig := &promconfig.Config{}

mb, err := yaml.Marshal(data.(map[any]any))
if err != nil {
return nil, err
}

err = yaml.Unmarshal(mb, pConfig)
if err != nil {
return nil, err
}
return pConfig, nil
}
}

// MapToLabelSelector returns a DecodeHookFuncType that
// provides a mechanism for decoding both matchLabels and matchExpressions from camelcase to lowercase
// because we use yaml unmarshaling that supports lowercase field names if no `yaml` tag is defined
// and metav1.LabelSelector uses `json` tags.
// If both the camelcase and lowercase version is present, then the camelcase version takes precedence.
func MapToLabelSelector() mapstructure.DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
if f.Kind() != reflect.Map {
return data, nil
}

if t != reflect.TypeOf(&metav1.LabelSelector{}) {
return data, nil
}

result := &metav1.LabelSelector{}
fMap := data.(map[any]any)
if matchLabels, ok := fMap["matchLabels"]; ok {
fMap["matchlabels"] = matchLabels
delete(fMap, "matchLabels")
}
if matchExpressions, ok := fMap["matchExpressions"]; ok {
fMap["matchexpressions"] = matchExpressions
delete(fMap, "matchExpressions")
}

b, err := yaml.Marshal(fMap)
if err != nil {
return nil, err
}

err = yaml.Unmarshal(b, result)
if err != nil {
return nil, err
}
return result, nil
}
}

func LoadFromFile(file string, target *Config) error {
return unmarshal(target, file)
}
Expand Down Expand Up @@ -153,14 +250,41 @@ func LoadFromCLI(target *Config, flagSet *pflag.FlagSet) error {
return nil
}

// unmarshal decodes the contents of the configFile into the cfg argument, using a
// mapstructure decoder with the following notable behaviors.
// Decodes time.Duration from strings (see StringToModelDurationHookFunc).
// Allows custom unmarshaling for promconfig.Config struct that implements yaml.Unmarshaler (see MapToPromConfig).
// Allows custom unmarshaling for metav1.LabelSelector struct using both camelcase and lowercase field names (see MapToLabelSelector).
func unmarshal(cfg *Config, configFile string) error {
yamlFile, err := os.ReadFile(configFile)
if err != nil {
return err
}
if err = yaml.Unmarshal(yamlFile, cfg); err != nil {

m := make(map[string]interface{})
err = yaml.Unmarshal(yamlFile, &m)
if err != nil {
return fmt.Errorf("error unmarshaling YAML: %w", err)
}

dc := mapstructure.DecoderConfig{
TagName: "yaml",
Result: cfg,
DecodeHook: mapstructure.ComposeDecodeHookFunc(
StringToModelDurationHookFunc(),
MapToPromConfig(),
MapToLabelSelector(),
),
}

decoder, err := mapstructure.NewDecoder(&dc)
if err != nil {
return err
}
if err := decoder.Decode(m); err != nil {
return err
}

return nil
}

Expand Down
Loading

0 comments on commit f26a319

Please sign in to comment.