Purpose: For developers, explains how the Go code creates a native v2 cluster configuration during opencenter cluster init.
This note describes how the current Go code creates a native v2 cluster configuration. It intentionally ignores the user-facing docs because several of them are stale.
Main distinction
opencenter cluster init creates the v2 cluster config YAML.
opencenter cluster generate does not create the config. It loads an existing
v2 config and renders GitOps, application, infrastructure, and OpenTofu output
from it.
Config creation path
The main entrypoint is cmd/cluster_init.go.
-
newClusterInitCmddefines the command and flags. -
runClusterInitbuilds the DI app, parses flags withparseInitOptions, checks provider availability, and callsInitService.Initialize. -
parseInitOptionshandles:-
positional cluster name
-
organization/clusteridentifiers -
--org -
--type -
--config-file -
--strict -
--force -
--no-keygen -
--no-sops-keygen -
--regenerate-keys -
--full-schema -
--kind-disable-default-cni -
unknown dotted override keys, for example
opencenter.infrastructure.compute.worker_count=5
-
-
parseInitOptionssetsSchemaVersionto2.0.
The business logic lives in internal/cluster/init_service.go.
InitService.Initialize performs the flow:
-
Validate cluster name and organization.
-
Default empty organization to
opencenter. -
Resolve the org-based path layout.
-
Check whether the cluster already exists, unless
--forceis set. -
Load an explicit config file or create a new default v2 config.
-
Apply explicit overrides and selected CLI config defaults.
-
Replace placeholder paths with resolved cluster-owned paths.
-
Optionally validate in strict mode.
-
Create cluster directories.
-
Generate SOPS and SSH keys unless disabled.
-
Save the config YAML.
-
Initialize a git repo unless disabled.
Default v2 object construction
The actual native config object is built in
internal/config/v2/defaults.go.
v2.NewV2Default(name, provider) is the primary constructor. It creates a
*v2.Config with:
-
schema_version: "2.0" -
system metadata timestamps and creator
-
opencenter.meta -
opencenter.cluster -
opencenter.infrastructure -
opencenter.gitops -
opencenter.services -
opencenter.managed_services -
deployment -
opentofu -
secrets
It then applies provider-specific defaults:
-
applyProviderCloudDefaultspopulates provider cloud blocks, currently OpenStack and VMware. -
applyProviderBehaviorDefaultsmutates behavior for providers such as Kind. -
applyGitOpsAuthDefaultschooses SSH or token Git auth defaults.
v2.NewV2FullTemplate(name, provider) wraps NewV2Default and adds a broader
set of explicit template fields for --full-schema.
CLI defaults
v2.NewV2Default calls loadCLIDefaults, which reads:
$OPENCENTER_CONFIG_DIR/config.yaml
or, when OPENCENTER_CONFIG_DIR is unset:
~/.config/opencenter/config.yaml
Currently, the defaults that affect initial construction are:
-
provider
-
region
-
environment
-
gitops_auth_method -
ssh_authorized_keys
The struct also reads base_domain, admin_email, kubernetes_version,
cni, and ssh_user, but those values are not wired into
NewV2Default today.
InitService.applyOverrides also reads the loaded CLI config through
ConfigManager and can replace default region/environment values after the
base config is created.
Path layout
Path resolution is org-based and lives in internal/core/paths/strategies.go.
For a cluster named my-cluster in org my-org, the resolved layout is:
<clustersDir>/my-org/.my-cluster-config.yaml
<clustersDir>/my-org/infrastructure/clusters/my-cluster/
<clustersDir>/my-org/applications/overlays/my-cluster/
<clustersDir>/my-org/secrets/
<clustersDir>/my-org/secrets/age/keys/my-cluster-key.txt
<clustersDir>/my-org/secrets/ssh/my-cluster
<clustersDir>/my-org/.sops.yaml
The clusters root comes from config.ResolveClustersDir:
-
OPENCENTER_CLUSTERS_DIR -
paths.clustersDirin the CLI config -
$OPENCENTER_CONFIG_DIR/clusters -
the default config dir plus
/clusters
InitService.updateConfigPaths writes these resolved paths back into the v2
config, including:
-
opencenter.gitops.repository.local_dir -
opencenter.infrastructure.ssh.key_path -
secrets.ssh_key.private -
secrets.ssh_key.public -
secrets.sops_age_key_file -
secrets.sops.age_key_file
It also rewrites Git auth defaults based on the effective gitops_auth_method.
Overrides
cluster init accepts normal Cobra flags and also accepts dotted override
flags because the command has UnknownFlags: true.
Unknown flags without dots are rejected. Unknown dotted flags are collected and
passed to configflags.NewCLIIntegration().ProcessFlags.
Example:
opencenter cluster init my-cluster \
--org my-org \
--type openstack \
opencenter.infrastructure.compute.worker_count=5
The override logic updates both the typed v2.Config and a map[string]any
copy used to track whether a value was explicit. That explicit-value tracking
prevents path and auth defaults from overwriting user-supplied values.
Saving and validation
Saving uses internal/config/v2/loader.go.
ConfigLoader.SaveToFile marshals the v2.Config to YAML, ensures the document
starts with ---, and writes it atomically with 0600 permissions.
Strict init validation calls InitService.validateConfig, which marshals the
typed config and runs it through the v2 loader pipeline:
load YAML
normalize
resolve references
apply defaults
validate
freeze
The v2 loader uses KnownFields(true), so unknown YAML fields fail parsing.
Guided configure path
opencenter cluster configure --guided can create or update the same v2 config.
The command lives in cmd/cluster_configure.go; the flow lives in
internal/cluster/configure_service.go.
When the config does not exist, ConfigureService.loadOrCreateConfig reuses the
same init internals:
-
initService.createDefaultConfig -
initService.applyOverrides -
initService.updateConfigPaths
Then the provider orchestrator and capability handlers discover values, prompt the user, build patches, apply those patches to the typed config, review the changes, generate keys, validate, and save.
For OpenStack, the provider-specific guided logic is in
internal/cluster/openstack_configure_orchestrator.go.
Generate path
opencenter cluster generate consumes an existing config.
The command lives in cmd/cluster_generate.go; the main service flow lives in
internal/cluster/setup_service.go.
SetupService.Setup:
-
Resolves cluster paths.
-
Loads the v2 config through
ConfigurationManager. -
Confirms
schema_versionis2.0. -
Checks that
opencenter.gitops.repository.local_diris set. -
Optionally validates setup config.
-
Renders outputs.
-
Validates generated manifests.
-
Commits changes unless this is a dry run.
The render step calls:
gitops.CopyBase(cfg, true)
gitops.RenderClusterApps(cfg)
gitops.RenderInfrastructureCluster(cfg)
tofu.Provision(cfg) // skipped for kind in SetupService
cluster generate --render-only follows a similar render-only path in
cmd/cluster_render.go.
Mental model
cluster init / cluster configure --guided
-> v2.NewV2Default or v2.NewV2FullTemplate
-> apply overrides
-> resolve cluster-owned paths
-> generate keys
-> write .<cluster>-config.yaml
cluster generate
-> load .<cluster>-config.yaml
-> render GitOps base
-> render cluster apps
-> render infrastructure cluster
-> render/provision OpenTofu