This is an automated email from the ASF dual-hosted git repository.

villebro pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/superset-kubernetes-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 4b997ec  chore: address probes, docs, RBAC, samples (#49)
4b997ec is described below

commit 4b997ec5c138138650abdae85a8a308c1bddd747
Author: Ville Brofeldt <[email protected]>
AuthorDate: Tue May 12 11:22:15 2026 -0700

    chore: address probes, docs, RBAC, samples (#49)
---
 .../superset-operator/templates/clusterrole.yaml   | 15 +++-----
 .../superset_v1alpha1_supersetcelerybeat.yaml      |  2 +-
 docs/user-guide/configuration.md                   | 24 +++++++++---
 internal/controller/child_controllers.go           | 43 +++++++++++++++++++++-
 internal/controller/deployment_builder.go          | 29 +++++++++++++--
 internal/controller/superset_controller.go         |  1 +
 6 files changed, 94 insertions(+), 20 deletions(-)

diff --git a/charts/superset-operator/templates/clusterrole.yaml 
b/charts/superset-operator/templates/clusterrole.yaml
index ebc05d2..0e6cea1 100644
--- a/charts/superset-operator/templates/clusterrole.yaml
+++ b/charts/superset-operator/templates/clusterrole.yaml
@@ -23,9 +23,6 @@ rules:
   - apiGroups: [""]
     resources: [configmaps, serviceaccounts, services]
     verbs: [create, delete, get, list, patch, update, watch]
-  - apiGroups: [""]
-    resources: [events]
-    verbs: [create, patch]
   - apiGroups: [""]
     resources: [pods]
     verbs: [create, delete, get, list, watch]
@@ -35,6 +32,9 @@ rules:
   - apiGroups: [autoscaling]
     resources: [horizontalpodautoscalers]
     verbs: [create, delete, get, list, patch, update, watch]
+  - apiGroups: [events.k8s.io]
+    resources: [events]
+    verbs: [create, patch, update]
   - apiGroups: [gateway.networking.k8s.io]
     resources: [httproutes]
     verbs: [create, delete, get, list, patch, update, watch]
@@ -47,15 +47,12 @@ rules:
   - apiGroups: [policy]
     resources: [poddisruptionbudgets]
     verbs: [create, delete, get, list, patch, update, watch]
-  - apiGroups: [superset.apache.org]
-    resources: [supersets]
-    verbs: [get, list, watch]
   - apiGroups: [superset.apache.org]
     resources: [supersetcelerybeats, supersetceleryflowers, 
supersetceleryworkers, supersetlifecycletasks, supersetmcpservers, 
supersetwebservers, supersetwebsocketservers]
     verbs: [create, delete, get, list, patch, update, watch]
   - apiGroups: [superset.apache.org]
-    resources: [supersetcelerybeats/status, supersetceleryflowers/status, 
supersetceleryworkers/status, supersetlifecycletasks/status, 
supersetmcpservers/status, supersetwebservers/status, 
supersetwebsocketservers/status]
+    resources: [supersetcelerybeats/status, supersetceleryflowers/status, 
supersetceleryworkers/status, supersetlifecycletasks/status, 
supersetmcpservers/status, supersets/status, supersetwebservers/status, 
supersetwebsocketservers/status]
     verbs: [get, patch, update]
   - apiGroups: [superset.apache.org]
-    resources: [supersets/status]
-    verbs: [get, patch, update]
+    resources: [supersets]
+    verbs: [get, list, watch]
diff --git a/config/samples/superset_v1alpha1_supersetcelerybeat.yaml 
b/config/samples/superset_v1alpha1_supersetcelerybeat.yaml
index daab42b..ad55dcd 100644
--- a/config/samples/superset_v1alpha1_supersetcelerybeat.yaml
+++ b/config/samples/superset_v1alpha1_supersetcelerybeat.yaml
@@ -5,7 +5,7 @@ metadata:
     app.kubernetes.io/name: superset
     app.kubernetes.io/component: celery-beat
     app.kubernetes.io/instance: superset-sample
-  name: superset-sample-celery-beat
+  name: superset-sample
 spec:
   image:
     tag: "latest"
diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md
index 964cc24..4779680 100644
--- a/docs/user-guide/configuration.md
+++ b/docs/user-guide/configuration.md
@@ -28,8 +28,9 @@ For lifecycle (migrations, upgrades), see 
[Lifecycle](lifecycle.md).
 The `environment` field controls validation strictness (enforced by
 [CEL](https://kubernetes.io/docs/reference/using-api/cel/) rules in the CRD 
schema):
 
-- **`prod`** (default) — inline `secretKey`, `metastore.uri`, 
`metastore.password`, and `valkey.password` are rejected by CRD validation. Use 
`secretKeyFrom`, `metastore.uriFrom`, `metastore.passwordFrom`, or 
`valkey.passwordFrom` to reference Kubernetes Secrets.
-- **`dev`** — allows plain-text `secretKey`, `metastore.uri`, 
`metastore.password`, and `valkey.password` directly in the CR for quick local 
development.
+- **`Production`** (default) — inline `secretKey`, `metastore.uri`, 
`metastore.password`, and `valkey.password` are rejected by CRD validation. Use 
`secretKeyFrom`, `metastore.uriFrom`, `metastore.passwordFrom`, or 
`valkey.passwordFrom` to reference Kubernetes Secrets.
+- **`Staging`** — same secret restrictions as Production, but allows 
`lifecycle.clone` for database cloning from an external source.
+- **`Development`** — allows plain-text `secretKey`, `metastore.uri`, 
`metastore.password`, and `valkey.password` directly in the CR for quick local 
development. Also permits `lifecycle.clone`, `lifecycle.init.adminUser`, and 
`lifecycle.init.loadExamples`.
 
 ### Dev Mode Example
 
@@ -406,11 +407,21 @@ spec:
 
 Enable Superset's async event streaming by setting `websocketServer`. This
 deploys a **Node.js** application (not Python) that pushes real-time updates to
-dashboards via WebSocket connections:
+dashboards via WebSocket connections.
+
+!!! warning "Requires a dedicated image"
+    The websocket server is a separate Node.js application and **does not run
+    from the default Superset image**. You must provide an image that contains
+    `websocket_server.js`. A community-maintained image is available at
+    
[`oneacrefund/superset-websocket`](https://hub.docker.com/r/oneacrefund/superset-websocket)
+    (experimental, not officially supported by Apache Superset).
 
 ```yaml
 spec:
-  websocketServer: {}
+  websocketServer:
+    image:
+      repository: oneacrefund/superset-websocket
+      tag: "latest"
 ```
 
 Because the websocket server is Node.js-based, it does **not** receive a
@@ -421,6 +432,9 @@ on the container template:
 ```yaml
 spec:
   websocketServer:
+    image:
+      repository: oneacrefund/superset-websocket
+      tag: "latest"
     podTemplate:
       container:
         env:
@@ -428,7 +442,7 @@ spec:
             value: "http://my-superset-web-server:8088";
 ```
 
-The websocket server creates a Service (default port 8088) and supports the
+The websocket server creates a Service (default port 8080) and supports the
 same scaling, deployment template, and pod template fields as other scalable
 components.
 
diff --git a/internal/controller/child_controllers.go 
b/internal/controller/child_controllers.go
index fab62fe..eb1cadf 100644
--- a/internal/controller/child_controllers.go
+++ b/internal/controller/child_controllers.go
@@ -21,6 +21,7 @@ package controller
 import (
        corev1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/util/intstr"
        "k8s.io/client-go/tools/events"
        "sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -28,6 +29,37 @@ import (
        "github.com/apache/superset-kubernetes-operator/internal/common"
 )
 
+var celeryBeatSingletonReplica = int32(1)
+
+func httpProbe(path string, port int32, initialDelay int32) *corev1.Probe {
+       return &corev1.Probe{
+               ProbeHandler: corev1.ProbeHandler{
+                       HTTPGet: &corev1.HTTPGetAction{
+                               Path: path,
+                               Port: intstr.FromInt32(port),
+                       },
+               },
+               InitialDelaySeconds: initialDelay,
+               PeriodSeconds:       10,
+               TimeoutSeconds:      5,
+               FailureThreshold:    3,
+       }
+}
+
+func tcpProbe(port int32, initialDelay int32) *corev1.Probe {
+       return &corev1.Probe{
+               ProbeHandler: corev1.ProbeHandler{
+                       TCPSocket: &corev1.TCPSocketAction{
+                               Port: intstr.FromInt32(port),
+                       },
+               },
+               InitialDelaySeconds: initialDelay,
+               PeriodSeconds:       10,
+               TimeoutSeconds:      5,
+               FailureThreshold:    3,
+       }
+}
+
 // 
+kubebuilder:rbac:groups=superset.apache.org,resources=supersetwebservers,verbs=get;list;watch;create;update;patch;delete
 // 
+kubebuilder:rbac:groups=superset.apache.org,resources=supersetwebservers/status,verbs=get;update;patch
 // 
+kubebuilder:rbac:groups=superset.apache.org,resources=supersetceleryworkers,verbs=get;list;watch;create;update;patch;delete
@@ -47,8 +79,6 @@ import (
 // 
+kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch;create;update;patch;delete
 // 
+kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete
 
-var celeryBeatSingletonReplica = int32(1)
-
 // ChildControllerDef defines the data needed to register a child controller.
 type ChildControllerDef struct {
        Name   string
@@ -69,6 +99,9 @@ func ChildControllerDefs() []ChildControllerDef {
                                        DefaultPorts: []corev1.ContainerPort{
                                                {Name: common.PortNameHTTP, 
ContainerPort: common.PortWebServer, Protocol: corev1.ProtocolTCP},
                                        },
+                                       DefaultLivenessProbe:  
httpProbe("/health", common.PortWebServer, 15),
+                                       DefaultReadinessProbe: 
httpProbe("/health", common.PortWebServer, 5),
+                                       DefaultStartupProbe:   
httpProbe("/health", common.PortWebServer, 15),
                                },
                                defaultPort: 0,
                                hasConfig:   true,
@@ -115,6 +148,8 @@ func ChildControllerDefs() []ChildControllerDef {
                                        DefaultPorts: []corev1.ContainerPort{
                                                {Name: common.PortNameHTTP, 
ContainerPort: common.PortCeleryFlower, Protocol: corev1.ProtocolTCP},
                                        },
+                                       DefaultLivenessProbe:  
httpProbe("/api/workers", common.PortCeleryFlower, 15),
+                                       DefaultReadinessProbe: 
httpProbe("/api/workers", common.PortCeleryFlower, 5),
                                },
                                defaultPort: common.PortCeleryFlower,
                                hasConfig:   true,
@@ -132,6 +167,8 @@ func ChildControllerDefs() []ChildControllerDef {
                                        DefaultPorts: []corev1.ContainerPort{
                                                {Name: 
common.PortNameWebsocket, ContainerPort: common.PortWebsocket, Protocol: 
corev1.ProtocolTCP},
                                        },
+                                       DefaultLivenessProbe:  
httpProbe("/health", common.PortWebsocket, 15),
+                                       DefaultReadinessProbe: 
httpProbe("/health", common.PortWebsocket, 5),
                                },
                                defaultPort: common.PortWebsocket,
                                hasConfig:   false,
@@ -149,6 +186,8 @@ func ChildControllerDefs() []ChildControllerDef {
                                        DefaultPorts: []corev1.ContainerPort{
                                                {Name: common.PortNameMcp, 
ContainerPort: common.PortMcpServer, Protocol: corev1.ProtocolTCP},
                                        },
+                                       DefaultLivenessProbe:  
tcpProbe(common.PortMcpServer, 15),
+                                       DefaultReadinessProbe: 
tcpProbe(common.PortMcpServer, 5),
                                },
                                defaultPort: common.PortMcpServer,
                                hasConfig:   true,
diff --git a/internal/controller/deployment_builder.go 
b/internal/controller/deployment_builder.go
index d35d0df..3522a76 100644
--- a/internal/controller/deployment_builder.go
+++ b/internal/controller/deployment_builder.go
@@ -54,6 +54,15 @@ type DeploymentConfig struct {
 
        // ForceReplicas, when non-nil, overrides the spec replicas (used for 
singletons like beat).
        ForceReplicas *int32
+
+       // DefaultLivenessProbe is used when the spec LivenessProbe is nil.
+       DefaultLivenessProbe *corev1.Probe
+
+       // DefaultReadinessProbe is used when the spec ReadinessProbe is nil.
+       DefaultReadinessProbe *corev1.Probe
+
+       // DefaultStartupProbe is used when the spec StartupProbe is nil.
+       DefaultStartupProbe *corev1.Probe
 }
 
 // buildDeploymentSpec constructs a complete DeploymentSpec from the flat 
child spec,
@@ -96,6 +105,20 @@ func buildDeploymentSpec(
                ports = ct.Ports
        }
 
+       // Resolve probes: user-provided takes precedence over component 
defaults.
+       livenessProbe := ct.LivenessProbe
+       if livenessProbe == nil {
+               livenessProbe = cfg.DefaultLivenessProbe
+       }
+       readinessProbe := ct.ReadinessProbe
+       if readinessProbe == nil {
+               readinessProbe = cfg.DefaultReadinessProbe
+       }
+       startupProbe := ct.StartupProbe
+       if startupProbe == nil {
+               startupProbe = cfg.DefaultStartupProbe
+       }
+
        // Build the main container from ContainerTemplate.
        mainContainer := corev1.Container{
                Name:            cfg.ContainerName,
@@ -106,9 +129,9 @@ func buildDeploymentSpec(
                EnvFrom:         ct.EnvFrom,
                VolumeMounts:    ct.VolumeMounts,
                Ports:           ports,
-               LivenessProbe:   ct.LivenessProbe,
-               ReadinessProbe:  ct.ReadinessProbe,
-               StartupProbe:    ct.StartupProbe,
+               LivenessProbe:   livenessProbe,
+               ReadinessProbe:  readinessProbe,
+               StartupProbe:    startupProbe,
                SecurityContext: ct.SecurityContext,
                Lifecycle:       ct.Lifecycle,
        }
diff --git a/internal/controller/superset_controller.go 
b/internal/controller/superset_controller.go
index eceedb1..3d6863a 100644
--- a/internal/controller/superset_controller.go
+++ b/internal/controller/superset_controller.go
@@ -208,6 +208,7 @@ func (r *SupersetReconciler) Reconcile(ctx context.Context, 
req ctrl.Request) (c
        }
 
        // Phase 5: Update aggregate status.
+       superset.Status.ConfigChecksum = configChecksum
        if err := r.updateStatus(ctx, superset); err != nil {
                return ctrl.Result{}, fmt.Errorf("updating status: %w", err)
        }

Reply via email to