| id | adding-services | ||||
|---|---|---|---|---|---|
| title | Adding New Platform Services | ||||
| sidebar_label | Adding Services | ||||
| description | How to add new platform services to openCenter-cli using auto-descriptors or explicit templates. | ||||
| doc_type | how-to | ||||
| audience | developers, platform engineers | ||||
| tags |
|
Purpose: For developers, shows how to add new platform services to openCenter-cli.
- Development environment set up (see Development Setup)
- Service's Helm chart already added to
openCenter-gitops-baseunderapplications/base/services/<service>/
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.
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{})
}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
go test ./internal/config/v2schema/ -run TestRegenSchemaControl 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) |
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"},
},
}},Services that need custom rendering logic (multi-component, conditional files, templated override-values, custom renderers) 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)
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: trueCreate the .tpl files referenced by the descriptor under internal/gitops/templates/cluster-apps-base/.
Add the service to the hardcoded lists in:
services/sources/kustomization.yaml.tplservices/fluxcd/kustomization.yaml.tpl
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.
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- 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