From c0c88b4597e979d1dcfcbd2a5eb5200484c3b671 Mon Sep 17 00:00:00 2001 From: Rohithmatham12 Date: Sat, 18 Jul 2026 13:28:40 -0400 Subject: [PATCH] Fix empty YAML validation for explicit zero values Signed-off-by: Rohithmatham12 --- cmd/cortex/main.go | 5 ++++ cmd/cortex/main_test.go | 5 ++++ pkg/cortex/cortex.go | 61 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/cmd/cortex/main.go b/cmd/cortex/main.go index d8a48677523..77c3f8fab56 100644 --- a/cmd/cortex/main.go +++ b/cmd/cortex/main.go @@ -259,6 +259,11 @@ func LoadConfig(filename string, expandENV bool, cfg *cortex.Config) error { buf = expandEnv(buf) } + err = cfg.SetYAMLRootNodeStates(buf) + if err != nil { + return errors.Wrap(err, "Error parsing config file") + } + err = yaml.UnmarshalStrict(buf, cfg) if err != nil { return errors.Wrap(err, "Error parsing config file") diff --git a/cmd/cortex/main_test.go b/cmd/cortex/main_test.go index abcb230d7bb..30f06067754 100644 --- a/cmd/cortex/main_test.go +++ b/cmd/cortex/main_test.go @@ -79,6 +79,11 @@ func TestFlagParsing(t *testing.T) { stderrMessage: "the Querier configuration in YAML has been specified as an empty YAML node", }, + "root level configuration option specified with explicit zero values": { + yaml: "flusher: { exit_after_flush: false }", + stderrExcluded: "empty YAML node", + }, + "version": { arguments: []string{"-version"}, stdoutMessage: "Cortex, version", diff --git a/pkg/cortex/cortex.go b/pkg/cortex/cortex.go index 658388e5ebb..8a2243746ae 100644 --- a/pkg/cortex/cortex.go +++ b/pkg/cortex/cortex.go @@ -98,6 +98,7 @@ type Config struct { PrintConfig bool `yaml:"-"` HTTPPrefix string `yaml:"http_prefix"` NameValidationScheme model.ValidationScheme `yaml:"name_validation_scheme"` + yamlRootNodeStates map[string]yamlRootNodeState ResourceMonitor configs.ResourceMonitor `yaml:"resource_monitor"` @@ -135,6 +136,54 @@ type Config struct { Tracing tracing.Config `yaml:"tracing"` } +type yamlRootNodeState int + +const ( + yamlRootNodeEmpty yamlRootNodeState = iota + yamlRootNodeNonEmpty +) + +// SetYAMLRootNodeStates records whether root-level YAML config keys were empty. +// This lets validation distinguish "querier:" from explicit zero values such +// as "flusher: { exit_after_flush: false }" after unmarshalling. +func (c *Config) SetYAMLRootNodeStates(buf []byte) error { + var root map[interface{}]interface{} + if err := yaml.Unmarshal(buf, &root); err != nil { + return err + } + + c.yamlRootNodeStates = map[string]yamlRootNodeState{} + for key, value := range root { + name, ok := key.(string) + if !ok { + continue + } + + if isEmptyYAMLRootNode(value) { + c.yamlRootNodeStates[name] = yamlRootNodeEmpty + } else { + c.yamlRootNodeStates[name] = yamlRootNodeNonEmpty + } + } + + return nil +} + +func isEmptyYAMLRootNode(value interface{}) bool { + if value == nil { + return true + } + + switch typed := value.(type) { + case map[interface{}]interface{}: + return len(typed) == 0 + case []interface{}: + return len(typed) == 0 + default: + return false + } +} + // RegisterFlags registers flag. func (c *Config) RegisterFlags(f *flag.FlagSet) { c.Server.MetricsNamespace = "cortex" @@ -296,6 +345,12 @@ func (c *Config) validateYAMLEmptyNodes() error { // If the struct has been reset due to empty YAML value and the zero struct value // doesn't match the default one, then we should warn the user about the issue. if cfgStruct.Field(i).Kind() == reflect.Struct && cfgStruct.Field(i).IsZero() && !defStruct.Field(i).IsZero() { + if c.yamlRootNodeStates != nil { + yamlName := yamlFieldName(cfgStruct.Type().Field(i)) + if c.yamlRootNodeStates[yamlName] == yamlRootNodeNonEmpty { + continue + } + } return fmt.Errorf("the %s configuration in YAML has been specified as an empty YAML node", cfgStruct.Type().Field(i).Name) } } @@ -303,6 +358,12 @@ func (c *Config) validateYAMLEmptyNodes() error { return nil } +func yamlFieldName(field reflect.StructField) string { + tag := field.Tag.Get("yaml") + name, _, _ := strings.Cut(tag, ",") + return name +} + func (c *Config) registerServerFlagsWithChangedDefaultValues(fs *flag.FlagSet) { throwaway := flag.NewFlagSet("throwaway", flag.PanicOnError)