Skip to content

Bug: config hot-reload validates before applying defaults, inverting startup order #9059

Description

@MengjiaLiang

Description:
The EnvoyGateway config loader applies validation and defaulting in opposite orders depending on whether the config is loaded at startup or via hot-reload (file-watcher reload).
This makes the validator contract ambiguous — validators silently must not depend on defaulted fields to keep hot-reload working, which is fragile and easy for new contributors to break.

Startup path
Defaults first, then validate:

  eg, err := config.Decode(cfgPath)
  eg.SetEnvoyGatewayDefaults()
  eg.Logging.SetEnvoyGatewayLoggingDefaults()
  ...
  if err := cfg.Validate(); err != nil { ... }

Hot-reload path
validate first, then defaults:

  eg, err := config.Decode(r.cfgPath)
  if err := validation.ValidateEnvoyGateway(eg); err != nil { ... }
  // Set defaults for unset fields
  eg.SetEnvoyGatewayDefaults()
  eg.Logging.SetEnvoyGatewayLoggingDefaults()

It happens to work today because the current validators short-circuit on nil/empty values (e.g. validateEnvoyGatewayKubernetesProvider returns nil if provider.Watch == nil) rather than reading defaulted fields.
As soon as a validator is added that reasonably assumes a field has been defaulted (e.g. switches on a defaulted enum, or checks a numeric default), it will pass at startup but produce false errors — or miss real errors — on hot-reload.

Expected behavior: validation should always run against a fully-defaulted struct, regardless of whether the config was loaded at startup or via hot-reload. The two code paths should apply defaults → validate in the same order.

Repro steps:

  1. Start envoy-gateway with --config-path pointing at a valid EnvoyGateway config file.
  2. Inspect internal/envoygateway/config/loader/configloader.go:78-85 to confirm ValidateEnvoyGateway runs before SetEnvoyGatewayDefaults.
  3. As a thought experiment, add a validator that reads a field populated only by SetEnvoyGatewayDefaults (e.g. asserting eg.Provider.Type == ProviderTypeKubernetes when Provider is unset by the user). It will pass at startup (because defaults run first in server.go) but fail or behave inconsistently when the same config is reloaded via the file watcher.

Happy to send a PR if maintainers agree this is the right direction.

Metadata

Metadata

Assignees

Labels

area/configIssues related to config management, e.g. Config Manager, Config Sources, etc.

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions