Skip to main content

Validation System Internals

Purpose: For contributors, explains the CLI validation pipeline and how to add new validators.

What the validation system does

The CLI validates cluster configurations before generating infrastructure artifacts. Validation catches misconfigurations early — before Terraform or Kubespray runs — reducing feedback loops from minutes to seconds.

Validation runs automatically during opencenter cluster validate and as a pre-check in opencenter cluster setup.

Two validation layers

1. Struct validation (go-playground/validator)

The primary validation mechanism uses struct tags from go-playground/validator/v10. Tags are declared directly on the config struct fields in internal/config/.

type ClusterConfig struct {
ClusterName string `yaml:"cluster_name" validate:"required,dns_rfc1035"`
Provider string `yaml:"provider" validate:"required,oneof=openstack vmware aws kind"`
KubeVersion string `yaml:"kube_version" validate:"required,semver"`
}

Common validator tags used in the CLI:

TagMeaning
requiredField must not be zero-value
oneof=a b cValue must be one of the listed options
semverMust be a valid semantic version
urlMust be a valid URL
cidrv4Must be a valid IPv4 CIDR
ipMust be a valid IP address
min=N, max=NNumeric or length bounds
dns_rfc1035Valid DNS label

2. JSON Schema validation

The CLI generates a JSON Schema from the Go structs (via invopop/jsonschema) and stores it in schema/cluster.schema.json. This schema is used for:

  • Editor autocompletion and inline validation (VS Code, etc.)
  • External tooling that consumes cluster configs
  • Documentation of the configuration format

Generate the schema:

mise run schema

Validation pipeline

When opencenter cluster validate runs, the pipeline executes in order:

  1. Load the YAML config file
  2. Unmarshal into the ClusterConfig struct
  3. Run go-playground/validator on the struct (checks all validate tags)
  4. Run cross-field validators (e.g., "if provider is openstack, then auth_url is required")
  5. Run provider-specific validators (e.g., OpenStack endpoint reachability)
  6. Report all errors with field paths and human-readable messages

Errors include the full field path so you can locate the problem in the YAML:

validation error: opencenter.infrastructure.cloud.openstack.auth_url is required when provider is "openstack"

Adding a new validator

To add validation for a new config field:

  1. Add the validate struct tag to the field in internal/config/
  2. If the built-in tags aren't sufficient, register a custom validator function with the validator instance
  3. Add a test in the corresponding *_test.go that verifies both valid and invalid inputs
  4. Run mise run schema to regenerate the JSON Schema

For cross-field validation (where one field's validity depends on another), add logic to the validation pipeline in internal/config/ rather than using struct tags.

Trade-offs

  • Struct tags handle ~90% of validation needs with zero custom code. They're declarative and self-documenting.
  • Cross-field validators require imperative code but handle conditional requirements that tags can't express.
  • JSON Schema provides external validation but lags behind the Go struct if you forget to regenerate it. The mise run schema task should be part of any config change workflow.

Common misconceptions

  • Validation doesn't modify the config. It only reports errors. Default values are applied during config loading, before validation runs.
  • The validate tag and yaml tag are independent. A field can be optional in YAML (omitempty) but required by validation (required) — this means the field has a default that gets set during loading.
  • Custom validators registered with go-playground/validator are available in struct tags just like built-in ones. Check internal/config/ for examples of custom tag registration.