Wiring upstream CRs
The wiring layer lives in internal/wiring/ and owns the translation from Palena CRs → upstream CRs. All external CRs are created as unstructured.Unstructured objects because Palena does not import the upstream operators' Go types — that would explode our module graph and create pinning hell.
Design rules
- Pure functions. Every
Build*function takes a Palena CR and returns an*unstructured.Unstructured. Nocontext.Context, noclient.Client, no I/O. This makes them trivially testable. omitemptysemantics. Optional fields are included in the output map only when the input has a non-nil / non-zero value. This keeps generated specs readable and prevents unnecessary reconciliation loops (empty fields default to upstream defaults).- GVK constants. The upstream
GroupVersionKindvalues live ininternal/wiring/unstructured.go— a single source of truth if an upstream operator ever bumps its API.
Target GVKs
| Source CR | Target GVK | Builder |
|---|---|---|
PalenaGateway | postgresql.cnpg.io/v1 Cluster | BuildCNPGCluster |
PalenaGateway | litellm.palena.ai/v1alpha1 LiteLLMInstance | BuildLiteLLMInstance |
PalenaModel | litellm.palena.ai/v1alpha1 LiteLLMModel | BuildLiteLLMModel |
PalenaObservability | langfuse.palena.ai/v1alpha1 LangfuseInstance | BuildLangfuseInstance |
CNPG Cluster
func BuildCNPGCluster(gw *PalenaGateway) *unstructured.Unstructured {
name := fmt.Sprintf("%s-palena-pg", gw.Name)
spec := map[string]interface{}{
"instances": gw.Spec.Database.Managed.Instances,
"storage": map[string]interface{}{
"size": gw.Spec.Database.Managed.StorageSize,
},
"bootstrap": map[string]interface{}{
"initdb": map[string]interface{}{
"database": "litellm",
"owner": "litellm",
},
},
}
if gw.Spec.Database.Managed.StorageClass != nil {
spec["storage"].(map[string]interface{})["storageClass"] = *gw.Spec.Database.Managed.StorageClass
}
if gw.Spec.Database.Managed.PostgreSQL != nil {
spec["postgresql"] = map[string]interface{}{
"parameters": gw.Spec.Database.Managed.PostgreSQL.Parameters,
}
}
if gw.Spec.Database.Managed.Backup != nil && gw.Spec.Database.Managed.Backup.Enabled {
spec["backup"] = map[string]interface{}{
"barmanObjectStore": /* ... */,
"retentionPolicy": gw.Spec.Database.Managed.Backup.RetentionPolicy,
}
}
return buildUnstructured(
CNPGClusterGVK, name, gw.Namespace, spec,
)
}Connection Secret
CNPG auto-creates a Secret named <cluster-name>-app with keys: host, port, dbname, user, password, uri, jdbc-uri. The LiteLLMInstance references this Secret; the Palena operator does not create or manage it.
Adding Langfuse's database
When a PalenaObservability CR is created, Langfuse needs a second database inside the same cluster. Palena uses CNPG's managed.databases field to add a langfuse database to the existing Cluster without recreating it.
LiteLLMInstance
func BuildLiteLLMInstance(gw *PalenaGateway, dbSecretName, redisSecretName string) *unstructured.Unstructured {
name := fmt.Sprintf("%s-palena-litellm", gw.Name)
spec := map[string]interface{}{
"replicas": gw.Spec.Gateway.Replicas,
"masterKey": map[string]interface{}{
"secretRef": map[string]interface{}{
"name": gw.Spec.Gateway.MasterKey.Name,
"key": gw.Spec.Gateway.MasterKey.Key,
},
},
"database": map[string]interface{}{
"cloudnativepg": map[string]interface{}{
"clusterRef": map[string]interface{}{
"name": fmt.Sprintf("%s-palena-pg", gw.Name),
"namespace": gw.Namespace,
},
"database": "litellm",
},
},
"redis": map[string]interface{}{
"external": map[string]interface{}{
"secretRef": map[string]interface{}{"name": redisSecretName},
"keys": map[string]interface{}{
"host": "host",
"port": "port",
"password": "password",
},
},
},
}
// Optional blocks: image, saltKey, configSync, routerSettings,
// generalSettings, autoscaling, ingress, route, resources, PDB,
// extraEnvVars — added only if the corresponding spec field is non-nil.
return buildUnstructured(LiteLLMInstanceGVK, name, gw.Namespace, spec)
}Every optional field follows the same pattern:
if gw.Spec.Gateway.SaltKey != nil {
spec["saltKey"] = map[string]interface{}{
"secretRef": map[string]interface{}{
"name": gw.Spec.Gateway.SaltKey.Name,
"key": gw.Spec.Gateway.SaltKey.Key,
},
}
}This keeps the generated spec clean and avoids re-reconciliation loops where the operator would otherwise keep setting an empty field that the upstream controller keeps removing.
LiteLLMModel
internal/wiring/litellm_model.go
func BuildLiteLLMModel(m *PalenaModel, gatewayName, litellmInstanceName string) *unstructured.Unstructured {
name := fmt.Sprintf("%s-%s", m.Spec.ModelName, gatewayName)
params := map[string]interface{}{
"model": m.Spec.LiteLLMParams.Model,
}
if m.Spec.LiteLLMParams.APIBase != "" {
params["apiBase"] = m.Spec.LiteLLMParams.APIBase
}
if m.Spec.LiteLLMParams.APIKeySecretRef != nil {
params["apiKeySecretRef"] = map[string]interface{}{
"name": m.Spec.LiteLLMParams.APIKeySecretRef.Name,
"key": m.Spec.LiteLLMParams.APIKeySecretRef.Key,
}
}
if m.Spec.LiteLLMParams.RPM != nil { params["rpm"] = *m.Spec.LiteLLMParams.RPM }
if m.Spec.LiteLLMParams.TPM != nil { params["tpm"] = *m.Spec.LiteLLMParams.TPM }
spec := map[string]interface{}{
"instanceRef": map[string]interface{}{"name": litellmInstanceName},
"modelName": m.Spec.ModelName,
"litellmParams": params,
}
return buildUnstructured(LiteLLMModelGVK, name, m.Namespace, spec)
}LangfuseInstance
func BuildLangfuseInstance(obs *PalenaObservability, dbClusterName string) *unstructured.Unstructured {
name := fmt.Sprintf("%s-palena-langfuse", obs.Name)
lf := obs.Spec.Langfuse
spec := map[string]interface{}{
"web": map[string]interface{}{"replicas": lf.Web.Replicas},
"database": map[string]interface{}{
"cloudnativepg": map[string]interface{}{
"clusterRef": map[string]interface{}{
"name": dbClusterName,
"namespace": obs.Namespace,
},
"database": "langfuse",
},
},
"blobStorage": buildBlobStorageSpec(lf.BlobStorage),
}
if lf.ClickHouse.Managed != nil {
spec["clickhouse"] = map[string]interface{}{
"managed": map[string]interface{}{
"replicas": lf.ClickHouse.Managed.Replicas,
"storageSize": lf.ClickHouse.Managed.StorageSize,
},
}
} else if lf.ClickHouse.External != nil {
spec["clickhouse"] = /* external config */
}
// Image, Auth, Ingress, Route, Worker, Resources — same optional pattern.
return buildUnstructured(LangfuseInstanceGVK, name, obs.Namespace, spec)
}Langfuse callback wiring
internal/wiring/langfuse_callback.go
Once the LangfuseInstance is Ready and its seeded API keys have been stored in a Secret, the observability controller calls:
func WireLangfuseCallback(ctx context.Context, c client.Client,
litellmInstance *unstructured.Unstructured,
langfuseEndpoint string,
langfuseAPIKeySecret string,
) error {
spec := litellmInstance.Object["spec"].(map[string]interface{})
spec["callbacks"] = map[string]interface{}{
"langfuse": map[string]interface{}{
"enabled": true,
"secretRef": map[string]interface{}{
"name": langfuseAPIKeySecret,
"keys": map[string]interface{}{
"publicKey": "LANGFUSE_PUBLIC_KEY",
"secretKey": "LANGFUSE_SECRET_KEY",
"host": "LANGFUSE_HOST",
},
},
},
}
return c.Update(ctx, litellmInstance)
}Langfuse API key Secret
Managed by Palena (owner reference → PalenaObservability):
apiVersion: v1
kind: Secret
metadata:
name: <obs-name>-langfuse-apikeys
data:
LANGFUSE_PUBLIC_KEY: <from langfuse init>
LANGFUSE_SECRET_KEY: <from langfuse init>
LANGFUSE_HOST: <langfuse endpoint>On PalenaObservability deletion, the finalizer removes the callbacks.langfuse block from the LiteLLMInstance before allowing itself to be GC'd — otherwise LiteLLM would keep trying to POST traces to a non-existent endpoint.
Helpers
buildUnstructured
func buildUnstructured(gvk schema.GroupVersionKind, name, namespace string, spec map[string]interface{}) *unstructured.Unstructured {
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(gvk)
obj.SetName(name)
obj.SetNamespace(namespace)
obj.Object["spec"] = spec
return obj
}Reading external CR status
func getCRStatus(ctx context.Context, c client.Client, gvk schema.GroupVersionKind, name, namespace string) (map[string]interface{}, error) {
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(gvk)
if err := c.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, obj); err != nil {
return nil, err
}
status, _ := obj.Object["status"].(map[string]interface{})
return status, nil
}
func isCRReady(status map[string]interface{}) bool {
conditions, _ := status["conditions"].([]interface{})
for _, c := range conditions {
cond, _ := c.(map[string]interface{})
if cond["type"] == "Ready" && cond["status"] == "True" {
return true
}
}
return false
}These helpers are the only place Palena looks into upstream CR status — everywhere else it treats them as opaque blobs.