add type annotations to opaque CRD schema fields (#7952)

* fix: add type annotations to opaque CRD schema fields

The WindmillInstance CRD failed K8s structural schema validation because
four `Option<serde_json::Value>` fields (`object_store_cache_config`,
`secret_backend`, `slack`, `teams`) generated schemas without a `type`
property. Kubernetes requires all specified object fields to declare a
type.

Add a `schemars(schema_with)` annotation that emits
`{"type": "object", "nullable": true, "x-kubernetes-preserve-unknown-fields": true}`
for these opaque settings fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: enable operator in CE builds

The operator feature was only included in `ee_core`, meaning CE images
couldn't run `windmill operator` as a proper k8s controller — the
subcommand silently fell through to standalone mode.

Add `operator` to `ce_core` so CE builds include the k8s controller
that watches and reconciles WindmillInstance CRDs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-02-15 12:26:21 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 62d045681f
commit ab81bddb91
2 changed files with 30 additions and 1 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ oss_core = [
"static_frontend", "mcp", "bedrock", "inline_preview",
"quickjs"
]
ce_core = ["oss_core", "private"]
ce_core = ["oss_core", "private", "operator"]
ee_core = [
"enterprise", "stripe", "prometheus", "cloud",
"kafka", "sqs_trigger", "nats", "gcp_trigger",
@@ -164,6 +164,31 @@ pub struct InstanceConfig {
// Global settings
// ---------------------------------------------------------------------------
/// Generate a schema for opaque JSON objects (used for EE-private settings).
/// Produces `{"type": "object", "nullable": true}` so the CRD passes K8s
/// structural schema validation while still accepting any JSON object.
#[cfg(feature = "instance_config_schema")]
fn opaque_json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::Object.into()),
metadata: Some(Box::default()),
extensions: {
let mut m = schemars::Map::new();
m.insert(
"nullable".to_string(),
serde_json::Value::Bool(true),
);
m.insert(
"x-kubernetes-preserve-unknown-fields".to_string(),
serde_json::Value::Bool(true),
);
m
},
..Default::default()
}
.into()
}
/// Typed global settings with schema validation.
/// Known settings have explicit fields; unknown settings pass through via `extra`.
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
@@ -265,6 +290,7 @@ pub struct GlobalSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub otel_tracing_proxy: Option<OtelTracingProxySettings>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "instance_config_schema", schemars(schema_with = "opaque_json_schema"))]
pub object_store_cache_config: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub critical_error_channels: Option<Vec<CriticalErrorChannel>>,
@@ -277,10 +303,13 @@ pub struct GlobalSettings {
// Opaque settings (EE-private structs or no clear schema)
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "instance_config_schema", schemars(schema_with = "opaque_json_schema"))]
pub secret_backend: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "instance_config_schema", schemars(schema_with = "opaque_json_schema"))]
pub slack: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "instance_config_schema", schemars(schema_with = "opaque_json_schema"))]
pub teams: Option<serde_json::Value>,
/// Catch-all for settings not yet covered by typed fields.