Purpose: For developers, shows how to add new platform services to openCenter-cli.
Prerequisites
-
Development environment set up (see development-setup.md[Development Setup])
-
Service’s Helm chart already added to
openCenter-gitops-baseunderapplications/base/services/<service>/
Quick Path: Standard Services (Auto-Descriptor)
Most services follow the standard two-stage FluxCD pattern. For these, adding a service requires only configuration changes — no templates, no descriptor files, no Go code beyond a config struct.
Step 1: Register the Service Config Type
If the service has no custom fields beyond BaseConfig, it’s already registered via DefaultServiceConfig in internal/config/services/default_services.go. Just add the name:
defaults := []string{
// ... existing services
"my-service",
}
If the service needs custom fields (storage type, credentials, etc.), create a typed config:
// internal/config/services/my_service.go
package services
type MyServiceConfig struct {
BaseConfig `yaml:",inline"`
BucketName string `yaml:"bucket_name,omitempty" json:"bucket_name,omitempty"`
}
func init() {
registry.RegisterServiceConfig("my-service", MyServiceConfig{})
}
Step 2: Set Defaults
Add the service to internal/config/v2/defaults.go in defaultServiceMap():
"my-service": &services.DefaultServiceConfig{BaseConfig: services.BaseConfig{
Enabled: true,
Namespace: "my-service",
}},
That’s it. The auto-descriptor engine generates:
-
services/sources/opencenter-my-service.yaml(GitRepository) -
services/fluxcd/my-service.yaml(two-stage Kustomization) -
services/my-service/kustomization.yaml(overlay with secretGenerator) -
services/my-service/helm-values/override-values.yaml(placeholder) -
Entries in aggregate
kustomization.yamlfiles
BaseConfig Rendering Fields
Control rendering behavior via BaseConfig fields:
| Field | Default | Use When |
| --- | --- | --- |
| Namespace | (required) | Always set — target namespace |
| Edition | "" | Service has community/enterprise variants in gitops-base |
| SourceName | opencenter-<name> | Multiple services share one GitRepository (e.g. observability) |
| SingleStage | false | Service has no base in gitops-base (overlay-only) |
| BaseOnly | false | Service needs no cluster-specific overlay |
| HasOverrideValues | true (nil) | Set false to skip secretGenerator |
| EnterpriseRegistry | false | Service needs enterprise OCI registry credentials |
| CustomResources | [] | Extra files in overlay kustomization (HTTPRoutes, IPAddressPools, etc.) |
| ExtraDependencies | [] | Additional dependsOn for the base stage |
| ConditionalDependencies | [] | Dependencies gated on another service being enabled |
| OverrideDependsOn | [] | Override stage dependsOn (default: [<service>-base]) |
| OverrideValues | "" | Inline override-values content (default: empty placeholder) |
Examples
Observability sub-service (shared source):
"mimir": &services.DefaultServiceConfig{BaseConfig: services.BaseConfig{
Enabled: false,
Namespace: "observability",
SourceName: "opencenter-observability",
}},
Base-only service (no cluster customization):
"external-snapshotter": &services.DefaultServiceConfig{BaseConfig: services.BaseConfig{
Enabled: true,
Namespace: "external-snapshotter",
BaseOnly: true,
}},
Single-stage service (overlay-only, no base):
"gateway": &services.DefaultServiceConfig{BaseConfig: services.BaseConfig{
Enabled: true,
Namespace: "gateway",
SingleStage: true,
ExtraDependencies: []string{"gateway-api-base"},
}},
Service with conditional dependency:
"rbac-manager": &services.DefaultServiceConfig{BaseConfig: services.BaseConfig{
Enabled: true,
Namespace: "rbac-system",
BaseOnly: true,
ConditionalDependencies: []services.ConditionalDependency{
{Name: "kube-prometheus-stack-base", WhenEnabled: "kube-prometheus-stack"},
},
}},
Complex Services: Explicit Descriptors
Services that need custom rendering logic (multi-component, conditional files, templated override-values, custom renderers) use explicit descriptors.
When to Use Explicit Descriptors
-
Multi-component services (keycloak: 4 sub-stages)
-
Services with conditional file rendering (keycloak backup cronjob, region-specific patches)
-
Services with templated override-values (loki, tempo, openstack-ccm)
-
Services with custom renderers (cert-manager multi-credential DNS)
Step 1: Create Descriptor
Create internal/services/descriptors/data/service-<name>.yaml:
name: service-my-complex-service
layer: services
service: my-complex-service
aggregate_targets:
- services-fluxcd-aggregate
- services-sources-aggregate
roots:
- path: services/my-complex-service
files:
- template: services/sources/opencenter-my-complex-service.yaml.tpl
- template: services/fluxcd/my-complex-service.yaml.tpl
- template: services/my-complex-service/conditional-file.yaml.tpl
when:
field: opencenter.services.my-complex-service.some_field
operator: true
Step 2: Create Templates
Create the .tpl files referenced by the descriptor under internal/gitops/templates/cluster-apps-base/.
Step 3: Add to Aggregate Lists
Add the service to the hardcoded lists in:
-
services/sources/kustomization.yaml.tpl -
services/fluxcd/kustomization.yaml.tpl
Step 4: (Optional) Custom Renderer
For services like cert-manager that generate dynamic files based on config:
// internal/gitops/my_service_renderer.go
func renderMyServiceDynamicFiles(cfg v2.Config, targetDir string, workspace *GitOpsWorkspace) error {
// Generate files based on typed config
}
Hook it into RenderClusterApps or the service plugin’s Renderer function.
Verification
After adding a service:
# Build
go build ./...
# Run tests
go test ./internal/gitops/ ./internal/config/... ./internal/services/...
# Regenerate schema
go test ./internal/config/v2schema/ -run TestRegenSchema
# Test rendering (dry-run)
./bin/opencenter cluster generate <org>/<cluster> --dry-run
Evidence
-
Auto-descriptor engine:
internal/gitops/auto_descriptor.go -
BaseConfig fields:
internal/config/services/base.go -
Service defaults:
internal/config/v2/defaults.go→defaultServiceMap() -
Explicit descriptors:
internal/services/descriptors/data/ -
Descriptor renderer:
internal/gitops/descriptor_renderer.go -
Rendering contract:
docs/dev/rendering-contract.md