Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/cortex/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
5 changes: 5 additions & 0 deletions cmd/cortex/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
61 changes: 61 additions & 0 deletions pkg/cortex/cortex.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -296,13 +345,25 @@ 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)
}
}

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)

Expand Down